Skip to content

Commit e9db591

Browse files
committed
"Red" test to prove null deref is possible.
Assisted-by: Claude Code / Opus 4.7
1 parent c30f221 commit e9db591

1 file changed

Lines changed: 119 additions & 0 deletions

File tree

tests/test_editAlgorithm.cpp

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)