Check for tile parts being too big in the verifier (DoM #2450).
authorCarl Hetherington <cth@carlh.net>
Sun, 16 Apr 2023 21:34:42 +0000 (23:34 +0200)
committerCarl Hetherington <cth@carlh.net>
Sun, 16 Apr 2023 21:34:42 +0000 (23:34 +0200)
src/verify.cc
src/verify.h
src/verify_j2k.cc
src/verify_j2k.h
test/verify_test.cc

index d43c7ff13abe88db51beb894c5b769c3e81ec6ec..81ca0bc9103b8e01fb88f047f99d8042c0ca750f 100644 (file)
@@ -449,7 +449,7 @@ verify_picture_asset (shared_ptr<const ReelFileAsset> reel_file_asset, boost::fi
                        biggest_frame = max(biggest_frame, frame->size());
                        if (!mono_asset->encrypted() || mono_asset->key()) {
                                vector<VerificationNote> j2k_notes;
-                               verify_j2k (frame, j2k_notes);
+                               verify_j2k(frame, i, mono_asset->frame_rate().numerator, j2k_notes);
                                check_and_add (j2k_notes);
                        }
                        progress (float(i) / duration);
@@ -461,8 +461,8 @@ verify_picture_asset (shared_ptr<const ReelFileAsset> reel_file_asset, boost::fi
                        biggest_frame = max(biggest_frame, max(frame->left()->size(), frame->right()->size()));
                        if (!stereo_asset->encrypted() || stereo_asset->key()) {
                                vector<VerificationNote> j2k_notes;
-                               verify_j2k (frame->left(), j2k_notes);
-                               verify_j2k (frame->right(), j2k_notes);
+                               verify_j2k(frame->left(), i, stereo_asset->frame_rate().numerator, j2k_notes);
+                               verify_j2k(frame->right(), i, stereo_asset->frame_rate().numerator, j2k_notes);
                                check_and_add (j2k_notes);
                        }
                        progress (float(i) / duration);
@@ -2014,6 +2014,11 @@ dcp::note_to_string (VerificationNote note)
                return String::compose("<MainSoundConfiguration> has an invalid value: %1", note.note().get());
        case VerificationNote::Code::MISSING_FONT:
                return String::compose("The font file for font ID \"%1\" was not found, or was not referred to in the ASSETMAP.", note.note().get());
+       case VerificationNote::Code::INVALID_JPEG2000_TILE_PART_SIZE:
+               return String::compose(
+                       "Frame %1 has an image component that is too large (component %2 is %3 bytes in size).",
+                       note.frame().get(), note.component().get(), note.size().get()
+                       );
        }
 
        return "";
index 8eec9749c44c3a121bea8ea10fc12ea12f9f0c23..4d65fae70442f184aed7f63f5ae6a4b76980b7d8 100644 (file)
@@ -436,7 +436,13 @@ public:
                /** An interop subtitle file has a <LoadFont> node which refers to a font file that is not found.
                 *  note contains the <LoadFont> ID
                 */
-               MISSING_FONT
+               MISSING_FONT,
+               /** A tile part in a JPEG2000 frame is too big.
+                *  frame contains the frame index (counted from 0)
+                *  component contains the component index (0, 1 or 2)
+                *  size contains the invalid size in bytes.
+                */
+               INVALID_JPEG2000_TILE_PART_SIZE,
        };
 
        VerificationNote (Type type, Code code)
@@ -485,9 +491,12 @@ public:
 
 private:
        enum class Data {
-               NOTE, ///< further information about the error
-               FILE, ///< path of file containing the error
-               LINE  ///< error line number within the FILE
+               NOTE,  ///< further information about the error
+               FILE,  ///< path of file containing the error
+               LINE,  ///< error line number within the FILE
+               FRAME,
+               COMPONENT,
+               SIZE,
        };
 
        template <class T>
@@ -513,6 +522,33 @@ public:
                return data<uint64_t>(Data::LINE);
        }
 
+       VerificationNote& set_frame(int frame) {
+               _data[Data::FRAME] = frame;
+               return *this;
+       }
+
+       boost::optional<int> frame() const {
+               return data<int>(Data::FRAME);
+       }
+
+       VerificationNote& set_component(int component) {
+               _data[Data::COMPONENT] = component;
+               return *this;
+       }
+
+       boost::optional<int> component() const {
+               return data<int>(Data::COMPONENT);
+       }
+
+       VerificationNote& set_size(int size) {
+               _data[Data::SIZE] = size;
+               return *this;
+       }
+
+       boost::optional<int> size() const {
+               return data<int>(Data::SIZE);
+       }
+
 private:
        Type _type;
        Code _code;
index 86ffb5b40d93d1714422b5b71116c9cfd9cfc321..b91588491e1e08242b2a748407fbab0ee4337a8d 100644 (file)
@@ -65,8 +65,11 @@ public:
 
 
 void
-dcp::verify_j2k (shared_ptr<const Data> j2k, vector<VerificationNote>& notes)
+dcp::verify_j2k(shared_ptr<const Data> j2k, int frame_index, int frame_rate, vector<VerificationNote>& notes)
 {
+       /* See ITU-T T800 (visible on https://github.com/Ymagis/ClairMeta/issues/130) */
+       unsigned int const max_tile_part_size = std::floor(200e6 / (8 * frame_rate));
+
        try {
                auto ptr = j2k->data();
                auto end = ptr + j2k->size();
@@ -202,8 +205,8 @@ dcp::verify_j2k (shared_ptr<const Data> j2k, vector<VerificationNote>& notes)
                        } else if (*marker_name == "SOT") {
                                require_16(10, "invalid SOT size %1");
                                get_16(); // tile index
-                               get_32(); // tile part length
-                               get_8(); // tile part index
+                               auto const tile_part_length = get_32();
+                               auto const tile_part_index = get_8();
                                auto tile_parts = get_8();
                                if (!fourk && tile_parts != 3) {
                                        notes.push_back ({ VerificationNote::Type::BV21_ERROR, VerificationNote::Code::INVALID_JPEG2000_TILE_PARTS_FOR_2K, raw_convert<string>(tile_parts) });
@@ -211,6 +214,13 @@ dcp::verify_j2k (shared_ptr<const Data> j2k, vector<VerificationNote>& notes)
                                if (fourk && tile_parts != 6) {
                                        notes.push_back ({ VerificationNote::Type::BV21_ERROR, VerificationNote::Code::INVALID_JPEG2000_TILE_PARTS_FOR_4K, raw_convert<string>(tile_parts) });
                                }
+                               if (tile_part_length > max_tile_part_size) {
+                                       VerificationNote note{VerificationNote::Type::ERROR, VerificationNote::Code::INVALID_JPEG2000_TILE_PART_SIZE};
+                                       note.set_frame(frame_index);
+                                       note.set_component(tile_part_index);
+                                       note.set_size(tile_part_length);
+                                       notes.push_back(note);
+                               }
                                main_header_finished = true;
                        } else if (*marker_name == "SOD") {
                                while (ptr < (end - 1) && (ptr[0] != 0xff || ptr[1] < 0x90)) {
index 3d9093dd2ee155f4374458e4ab4413e7717035ac..ac69155c61161002a53def8c68db2c71b95bd8d8 100644 (file)
@@ -51,7 +51,11 @@ namespace dcp {
 class Data;
 
 
-void verify_j2k (std::shared_ptr<const Data> data, std::vector<VerificationNote>& notes);
+/** @param frame_index Video frame index, so that notes can say which frame contains the problem.
+ *  @param frame_rate Video frame rate (in frames per second) to calculate how big the tile parts
+ *  can be.
+ */
+void verify_j2k(std::shared_ptr<const Data> data, int frame_index, int frame_rate, std::vector<VerificationNote>& notes);
 
 
 }
index 62e386b796de5421378302ad81601e39e7bf4053..e182f05d796d11254403efda3e6473efd299cf58 100644 (file)
@@ -58,8 +58,9 @@
 #include "util.h"
 #include "verify.h"
 #include "verify_j2k.h"
-#include <boost/test/unit_test.hpp>
 #include <boost/algorithm/string.hpp>
+#include <boost/random.hpp>
+#include <boost/test/unit_test.hpp>
 #include <cstdio>
 #include <iostream>
 
@@ -3087,7 +3088,7 @@ BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_2k)
        dcp::MonoPictureAsset picture (find_file(private_test / "data" / "JourneyToJah_TLR-1_F_EN-DE-FR_CH_51_2K_LOK_20140225_DGL_SMPTE_OV", "j2c.mxf"));
        auto reader = picture.start_read ();
        auto frame = reader->get_frame (0);
-       verify_j2k (frame, notes);
+       verify_j2k(frame, 0, 24, notes);
        BOOST_REQUIRE_EQUAL (notes.size(), 0U);
 }
 
@@ -3098,7 +3099,7 @@ BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_4k)
        dcp::MonoPictureAsset picture (find_file(private_test / "data" / "sul", "TLR"));
        auto reader = picture.start_read ();
        auto frame = reader->get_frame (0);
-       verify_j2k (frame, notes);
+       verify_j2k(frame, 0, 24, notes);
        BOOST_REQUIRE_EQUAL (notes.size(), 0U);
 }
 
@@ -3113,7 +3114,7 @@ BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_libdcp)
        dcp::MonoPictureAsset picture (find_file(dir, "video"));
        auto reader = picture.start_read ();
        auto frame = reader->get_frame (0);
-       verify_j2k (frame, notes);
+       verify_j2k(frame, 0, 24, notes);
        BOOST_REQUIRE_EQUAL (notes.size(), 0U);
 }
 
@@ -3576,3 +3577,64 @@ BOOST_AUTO_TEST_CASE(verify_invalid_main_sound_configuration)
                });
 }
 
+
+BOOST_AUTO_TEST_CASE(verify_invalid_tile_part_size)
+{
+       boost::filesystem::path const path = "build/test/verify_invalid_tile_part_size";
+       auto constexpr video_frames = 24;
+       auto constexpr sample_rate = 48000;
+
+       boost::filesystem::remove_all(path);
+       boost::filesystem::create_directories(path);
+
+       auto mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(24, 1), dcp::Standard::SMPTE);
+       auto picture_writer = mp->start_write(path / "video.mxf", dcp::PictureAsset::Behaviour::MAKE_NEW);
+
+       dcp::Size const size(1998, 1080);
+       auto image = make_shared<dcp::OpenJPEGImage>(size);
+       boost::random::mt19937 rng(1);
+       boost::random::uniform_int_distribution<> dist(0, 4095);
+       for (int c = 0; c < 3; ++c) {
+               for (int p = 0; p < (1998 * 1080); ++p) {
+                       image->data(c)[p] = dist(rng);
+               }
+       }
+       auto j2c = dcp::compress_j2k(image, 750000000, video_frames, false, false);
+       for (int i = 0; i < 24; ++i) {
+               picture_writer->write(j2c.data(), j2c.size());
+       }
+       picture_writer->finalize();
+
+       auto dcp = make_shared<dcp::DCP>(path);
+       auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
+       cpl->set_content_version(
+               dcp::ContentVersion("urn:uuid:75ac29aa-42ac-1234-ecae-49251abefd11", "content-version-label-text")
+               );
+       cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,R,C,LFE,Ls,Rs"));
+       cpl->set_main_sound_sample_rate(sample_rate);
+       cpl->set_main_picture_stored_area(dcp::Size(1998, 1080));
+       cpl->set_main_picture_active_area(dcp::Size(1998, 1080));
+       cpl->set_version_number(1);
+
+       auto ms = simple_sound(path, "", dcp::MXFMetadata(), "en-US", video_frames, sample_rate, {});
+
+       auto reel = make_shared<dcp::Reel>(
+               make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
+               make_shared<dcp::ReelSoundAsset>(ms, 0)
+               );
+
+       cpl->add(reel);
+       dcp->add(cpl);
+       dcp->set_annotation_text("A Test DCP");
+       dcp->write_xml();
+
+       check_verify_result(
+               { path },
+               {
+                       dcp::VerificationNote(dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_TILE_PART_SIZE).set_frame(0).set_component(0).set_size(1321721),
+                       { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_SIZE_IN_BYTES, canonical(path / "video.mxf") },
+                       { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC },
+                       { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC },
+               });
+}
+