@@ -2131,6 +2131,125 @@ main(int argc, char** argv)
21312131 TimeRange (RationalTime (0.0 , 24.0 ), RationalTime (10.0 , 24.0 )) });
21322132 });
21332133
2134+ // Null Pointer Dereference from Unchecked dynamic_cast
2135+ //
2136+ // The editAlgorithm functions call dynamic_cast<Item*>(item->clone()) and
2137+ // immediately dereference the result. SerializableObject::clone() can
2138+ // return nullptr (for example, when the object's metadata contains a
2139+ // cycle, which causes the cloning encoder to error out). When that
2140+ // happens, the dynamic_cast also yields nullptr and the next member call
2141+ // is a null pointer dereference (denial of service / crash).
2142+ //
2143+ // The tests below construct Items whose metadata contains a cycle (the
2144+ // clip references itself) and exercise each of the four affected call
2145+ // sites. After the fix, the algorithms must not crash and must report a
2146+ // non-OK error status rather than dereferencing nullptr.
2147+ //
2148+ // Helper: make a clip whose clone() will fail because of a metadata cycle.
2149+ auto make_clip_with_cyclic_metadata = [](std::string const & name,
2150+ TimeRange const & source_range)
2151+ -> SerializableObject::Retainer<Clip> {
2152+ SerializableObject::Retainer<Clip> clip =
2153+ new Clip (name, nullptr , source_range);
2154+ // Insert a self-reference in the metadata so that clone() (which
2155+ // round-trips through serialization) detects an OBJECT_CYCLE and
2156+ // returns nullptr.
2157+ clip->metadata ()[" self" ] = SerializableObject::Retainer<>(clip.value );
2158+ // Sanity check: cloning this clip should fail.
2159+ OTIO_NS ::ErrorStatus err;
2160+ SerializableObject* cloned = clip->clone (&err);
2161+ assertTrue (cloned == nullptr );
2162+ assertTrue (is_error (err));
2163+ return clip;
2164+ };
2165+
2166+ // Line 185: overwrite() splits a single clip whose middle is overwritten.
2167+ tests.add_test (" test_edit_overwrite_null_clone_safe" , [&] {
2168+ auto clip = make_clip_with_cyclic_metadata (
2169+ " cyclic" ,
2170+ TimeRange (RationalTime (0.0 , 24.0 ), RationalTime (24.0 , 24.0 )));
2171+ SerializableObject::Retainer<Track> track = new Track ();
2172+ track->append_child (clip);
2173+
2174+ SerializableObject::Retainer<Clip> insert_clip = new Clip (
2175+ " insert" ,
2176+ nullptr ,
2177+ TimeRange (RationalTime (0.0 , 24.0 ), RationalTime (4.0 , 24.0 )));
2178+
2179+ OTIO_NS ::ErrorStatus error_status;
2180+ // Overwrite a 4-frame range in the middle of the cyclic clip. This
2181+ // forces the code path that clones items.front() to produce the
2182+ // trailing slice (line 185).
2183+ algo::overwrite (
2184+ insert_clip,
2185+ track,
2186+ TimeRange (RationalTime (8.0 , 24.0 ), RationalTime (4.0 , 24.0 )),
2187+ true ,
2188+ nullptr ,
2189+ &error_status);
2190+ // Must not crash; must report an error.
2191+ assertTrue (is_error (error_status));
2192+ });
2193+
2194+ // Line 367: insert() splits an existing clip and clones it for the tail.
2195+ tests.add_test (" test_edit_insert_null_clone_safe" , [&] {
2196+ auto clip = make_clip_with_cyclic_metadata (
2197+ " cyclic" ,
2198+ TimeRange (RationalTime (0.0 , 24.0 ), RationalTime (24.0 , 24.0 )));
2199+ SerializableObject::Retainer<Track> track = new Track ();
2200+ track->append_child (clip);
2201+
2202+ SerializableObject::Retainer<Clip> insert_clip = new Clip (
2203+ " insert" ,
2204+ nullptr ,
2205+ TimeRange (RationalTime (0.0 , 24.0 ), RationalTime (4.0 , 24.0 )));
2206+
2207+ OTIO_NS ::ErrorStatus error_status;
2208+ algo::insert (
2209+ insert_clip,
2210+ track,
2211+ RationalTime (12.0 , 24.0 ),
2212+ true ,
2213+ nullptr ,
2214+ &error_status);
2215+ assertTrue (is_error (error_status));
2216+ });
2217+
2218+ // Line 534: slice() clones an item to create the second slice.
2219+ tests.add_test (" test_edit_slice_null_clone_safe" , [&] {
2220+ auto clip = make_clip_with_cyclic_metadata (
2221+ " cyclic" ,
2222+ TimeRange (RationalTime (0.0 , 24.0 ), RationalTime (24.0 , 24.0 )));
2223+ SerializableObject::Retainer<Track> track = new Track ();
2224+ track->append_child (clip);
2225+
2226+ OTIO_NS ::ErrorStatus error_status;
2227+ algo::slice (track, RationalTime (12.0 , 24.0 ), true , &error_status);
2228+ assertTrue (is_error (error_status));
2229+ });
2230+
2231+ // Line 795: fill() clones the source item before placing it on the track.
2232+ tests.add_test (" test_edit_fill_null_clone_safe" , [&] {
2233+ // Track with a gap so that fill() can find a slot to fill.
2234+ SerializableObject::Retainer<Gap> gap = new Gap (
2235+ TimeRange (RationalTime (0.0 , 24.0 ), RationalTime (24.0 , 24.0 )));
2236+ SerializableObject::Retainer<Track> track = new Track ();
2237+ track->append_child (gap);
2238+
2239+ auto clip = make_clip_with_cyclic_metadata (
2240+ " cyclic" ,
2241+ TimeRange (RationalTime (0.0 , 24.0 ), RationalTime (24.0 , 24.0 )));
2242+
2243+ OTIO_NS ::ErrorStatus error_status;
2244+ algo::fill (
2245+ clip,
2246+ track,
2247+ RationalTime (0.0 , 24.0 ),
2248+ ReferencePoint::Sequence,
2249+ &error_status);
2250+ assertTrue (is_error (error_status));
2251+ });
2252+
21342253 tests.run (argc, argv);
21352254 return 0 ;
21362255}
0 commit comments