diff --git a/defects/kdenlive/patch/kdenlive-0001-thumbnailcache-storedOnDisk-linear-find.patch b/defects/kdenlive/patch/kdenlive-0001-thumbnailcache-storedOnDisk-linear-find.patch new file mode 100644 index 000000000..bcbfe9d3e --- /dev/null +++ b/defects/kdenlive/patch/kdenlive-0001-thumbnailcache-storedOnDisk-linear-find.patch @@ -0,0 +1,44 @@ +# UNDF: (leave blank) +# Defect: kdenlive-0001 +# Component: src/utils/thumbnailcache.hpp + thumbnailcache.cpp +# Pattern: CWE-407 — m_storedOnDisk/m_storedVolatile vector with std::find for dedup +# Severity: MEDIUM — O(C*P*V) in saveCachedThumbs, O(F*V) in invalidateThumbsForClip +# Fix: Change vector to unordered_set for O(1) membership test +--- a/src/utils/thumbnailcache.hpp ++++ b/src/utils/thumbnailcache.hpp +@@ -16,6 +16,7 @@ + #include + #include + #include ++#include + #include + + /** @class ThumbnailCache +@@ -89,8 +90,8 @@ + + // the following maps keeps track of the positions that we store for each clip in volatile caches. + // Note that we don't track deletions due to items dropped from the cache. So the maps can contain more items that are currently stored. +- std::unordered_map> m_storedVolatile; +- mutable std::unordered_map> m_storedOnDisk; ++ std::unordered_map> m_storedVolatile; ++ mutable std::unordered_map> m_storedOnDisk; + }; +--- a/src/utils/thumbnailcache.cpp ++++ b/src/utils/thumbnailcache.cpp + // In all getThumbnail/storeThumbnail/saveCachedThumbs methods, replace: + // std::find(m_storedOnDisk[binId].begin(), m_storedOnDisk[binId].end(), pos) == m_storedOnDisk[binId].end() + // with: + // m_storedOnDisk[binId].find(pos) == m_storedOnDisk[binId].end() + // (or equivalently: m_storedOnDisk[binId].count(pos) == 0) + // + // Replace push_back(pos) with insert(pos). + // + // In invalidateThumbsForClip, replace std::find + erase with: + // cachedFrames.erase(f); // O(1) for unordered_set + // + // Same changes for m_storedVolatile. + // + // saveCachedThumbs signature change: + // void saveCachedThumbs(const std::unordered_map> &keys) + // The parameter can remain vector (caller provides frame list), + // but internal dedup uses the unordered_set. diff --git a/defects/kdenlive/patch/kdenlive-0002-timelinemodel-clipIds-mix-linear-find.patch b/defects/kdenlive/patch/kdenlive-0002-timelinemodel-clipIds-mix-linear-find.patch new file mode 100644 index 000000000..40e2885f5 --- /dev/null +++ b/defects/kdenlive/patch/kdenlive-0002-timelinemodel-clipIds-mix-linear-find.patch @@ -0,0 +1,35 @@ +# UNDF: (leave blank) +# Defect: kdenlive-0002 +# Component: src/timeline2/model/timelinemodel.cpp — requestClipsMixing +# Pattern: CWE-407 — clipIds vector scanned with std::find inside per-clip loop +# Severity: MEDIUM — O(N^2) where N = selected clips; multiple std::find per iteration +# Fix: Build unordered_set from clipIds for O(1) membership test +--- a/src/timeline2/model/timelinemodel.cpp ++++ b/src/timeline2/model/timelinemodel.cpp +@@ -1071,6 +1071,7 @@ + clipIds = ordered; ++ std::unordered_set clipIdSet(clipIds.begin(), clipIds.end()); + + int noSpaceInClip = 0; +@@ -1146,8 +1147,8 @@ +- if (std::find(clipIds.begin(), clipIds.end(), previousClip) != clipIds.end() && +- std::find(clipIds.begin(), clipIds.end(), mixInfo.clips.second) == clipIds.end()) { ++ if (clipIdSet.count(previousClip) && ++ !clipIdSet.count(mixInfo.clips.second)) { + continue; +@@ -1162,8 +1163,8 @@ +- if (std::find(clipIds.begin(), clipIds.end(), nextClip) != clipIds.end() && +- std::find(clipIds.begin(), clipIds.end(), mixInfo.clips.first) == clipIds.end()) { ++ if (clipIdSet.count(nextClip) && ++ !clipIdSet.count(mixInfo.clips.first)) { + continue; +@@ -1174,10 +1175,10 @@ +- if (std::find(clipIds.begin(), clipIds.end(), mixInfo.clips.second) == clipIds.end()) { +- if (std::find(clipIds.begin(), clipIds.end(), mixInfo.clips.first) != clipIds.end()) { ++ if (!clipIdSet.count(mixInfo.clips.second)) { ++ if (clipIdSet.count(mixInfo.clips.first)) { + mixInfo.clips.second = -1; + } +- } else if (std::find(clipIds.begin(), clipIds.end(), mixInfo.clips.first) == clipIds.end()) { ++ } else if (!clipIdSet.count(mixInfo.clips.first)) { + mixInfo.clips.first = -1; diff --git a/defects/kdenlive/patch/kdenlive-0003-timelinecontroller-sorted-clips-linear-find.patch b/defects/kdenlive/patch/kdenlive-0003-timelinecontroller-sorted-clips-linear-find.patch new file mode 100644 index 000000000..51f9bb35b --- /dev/null +++ b/defects/kdenlive/patch/kdenlive-0003-timelinecontroller-sorted-clips-linear-find.patch @@ -0,0 +1,22 @@ +# UNDF: (leave blank) +# Defect: kdenlive-0003 +# Component: src/timeline2/view/timelinecontroller.cpp — moveGroup +# Pattern: CWE-407 — sorted_clips vector scanned with std::find inside per-clip loop +# Severity: MEDIUM — O(N^2) where N = grouped clips being moved +# Fix: Build unordered_set from sorted_clips for O(1) membership test +--- a/src/timeline2/view/timelinecontroller.cpp ++++ b/src/timeline2/view/timelinecontroller.cpp +@@ -4638,6 +4638,7 @@ + std::vector sorted_clips{std::make_move_iterator(std::begin(all_items)), std::make_move_iterator(std::end(all_items))}; + std::sort(sorted_clips.begin(), sorted_clips.end(), [this](const int &clipId1, const int &clipId2) { + ... + }); ++ std::unordered_set sorted_clips_set(sorted_clips.begin(), sorted_clips.end()); + ... + for (int item : sorted_clips) { +@@ -4673,7 +4674,7 @@ +- if (std::find(sorted_clips.begin(), sorted_clips.end(), mixData.first.firstClipId) == sorted_clips.end()) { ++ if (sorted_clips_set.count(mixData.first.firstClipId) == 0) { +@@ -4685,7 +4686,7 @@ +- if (mixData.second.firstClipId > -1 && std::find(sorted_clips.begin(), sorted_clips.end(), mixData.second.secondClipId) == sorted_clips.end()) { ++ if (mixData.second.firstClipId > -1 && sorted_clips_set.count(mixData.second.secondClipId) == 0) { diff --git a/defects/kdenlive/patch/kdenlive-0004-timelinemodel-resize-all-items-linear-find.patch b/defects/kdenlive/patch/kdenlive-0004-timelinemodel-resize-all-items-linear-find.patch new file mode 100644 index 000000000..b89f269bd --- /dev/null +++ b/defects/kdenlive/patch/kdenlive-0004-timelinemodel-resize-all-items-linear-find.patch @@ -0,0 +1,32 @@ +# UNDF: (leave blank) +# Defect: kdenlive-0004 +# Component: src/timeline2/model/timelinemodel.cpp — requestClipResizeAndTimeWarp +# Pattern: CWE-407 — all_items std::list with std::find inside loop over currentSelection +# Severity: MEDIUM — O(N^2) where N = current selection size +# Fix: Use unordered_set for dedup check alongside the list +--- a/src/timeline2/model/timelinemodel.cpp ++++ b/src/timeline2/model/timelinemodel.cpp +@@ -3577,6 +3577,7 @@ + std::list all_items; + std::unordered_set selectionOnlyItems; ++ std::unordered_set all_items_set; + //first item has to be the item being resized + all_items.push_back(itemId); ++ all_items_set.insert(itemId); + bool isSplitItemPresent = false; +@@ -3591,6 +3592,7 @@ + all_items.push_back(splitId); ++ all_items_set.insert(splitId); + isSplitItemPresent = true; +@@ -3594,6 +3596,7 @@ + all_items.push_back(splitId); ++ all_items_set.insert(splitId); + isSplitItemPresent = true; +@@ -3603,7 +3606,7 @@ +- if (id == itemId || std::find(all_items.begin(), all_items.end(), id) != all_items.end() || !isClip(id)) { ++ if (id == itemId || all_items_set.count(id) || !isClip(id)) { + continue; + } + all_items.push_back(id); ++ all_items_set.insert(id); + selectionOnlyItems.insert(id); diff --git a/defects/kdenlive/unit/KdenliveTest.class b/defects/kdenlive/unit/KdenliveTest.class new file mode 100644 index 000000000..46bdd5f98 Binary files /dev/null and b/defects/kdenlive/unit/KdenliveTest.class differ diff --git a/defects/kdenlive/unit/KdenliveTest.java b/defects/kdenlive/unit/KdenliveTest.java new file mode 100644 index 000000000..4452b8d19 --- /dev/null +++ b/defects/kdenlive/unit/KdenliveTest.java @@ -0,0 +1,205 @@ +import java.util.*; + +/** + * CWE-407 simulation for Kdenlive defects. + * kdenlive-0001: ThumbnailCache storedOnDisk vector linear find for dedup + * kdenlive-0002: TimelineModel clipIds vector linear find in mix loop + * kdenlive-0003: TimelineController sorted_clips vector linear find in moveGroup + * kdenlive-0004: TimelineModel all_items list linear find in resize + */ +public class KdenliveTest { + + // --- kdenlive-0001: ThumbnailCache storedOnDisk dedup --- + + static long thumbnailCacheDefect(int numClips, int framesPerClip) { + // Simulates m_storedOnDisk as Map> + Map> storedOnDisk = new HashMap<>(); + long ops = 0; + for (int c = 0; c < numClips; c++) { + String binId = "clip-" + c; + storedOnDisk.put(binId, new ArrayList<>()); + for (int f = 0; f < framesPerClip; f++) { + List vec = storedOnDisk.get(binId); + // std::find linear scan before push_back + boolean found = false; + for (int existing : vec) { + ops++; + if (existing == f) { found = true; break; } + } + if (!found) { + vec.add(f); + } + } + } + return ops; + } + + static long thumbnailCacheFixed(int numClips, int framesPerClip) { + Map> storedOnDisk = new HashMap<>(); + long ops = 0; + for (int c = 0; c < numClips; c++) { + String binId = "clip-" + c; + storedOnDisk.put(binId, new HashSet<>()); + for (int f = 0; f < framesPerClip; f++) { + ops++; + storedOnDisk.get(binId).add(f); + } + } + return ops; + } + + // --- kdenlive-0002: clipIds mix membership --- + + static long clipIdsMixDefect(int N) { + List clipIds = new ArrayList<>(); + for (int i = 0; i < N; i++) clipIds.add(i); + long ops = 0; + for (int s : clipIds) { + // Simulate 3 std::find calls per clip (typical in mix logic) + for (int check = 0; check < 3; check++) { + int target = (s + 1) % N; + for (int id : clipIds) { + ops++; + if (id == target) break; + } + } + } + return ops; + } + + static long clipIdsMixFixed(int N) { + List clipIds = new ArrayList<>(); + Set clipIdSet = new HashSet<>(); + for (int i = 0; i < N; i++) { clipIds.add(i); clipIdSet.add(i); } + long ops = 0; + for (int s : clipIds) { + for (int check = 0; check < 3; check++) { + ops++; + int target = (s + 1) % N; + clipIdSet.contains(target); + } + } + return ops; + } + + // --- kdenlive-0003: sorted_clips moveGroup membership --- + + static long sortedClipsMoveDefect(int N) { + List sortedClips = new ArrayList<>(); + for (int i = 0; i < N; i++) sortedClips.add(i); + long ops = 0; + for (int item : sortedClips) { + // 2 std::find calls per clip (startMix + endMix check) + for (int check = 0; check < 2; check++) { + int target = (item + 1) % N; + for (int id : sortedClips) { + ops++; + if (id == target) break; + } + } + } + return ops; + } + + static long sortedClipsMoveFixed(int N) { + List sortedClips = new ArrayList<>(); + Set sortedSet = new HashSet<>(); + for (int i = 0; i < N; i++) { sortedClips.add(i); sortedSet.add(i); } + long ops = 0; + for (int item : sortedClips) { + for (int check = 0; check < 2; check++) { + ops++; + sortedSet.contains((item + 1) % N); + } + } + return ops; + } + + // --- kdenlive-0004: all_items resize dedup --- + + static long allItemsResizeDefect(int N) { + // currentSelection has N items; all_items grows as we add non-duplicates + LinkedList allItems = new LinkedList<>(); + allItems.add(0); // itemId + long ops = 0; + for (int id = 1; id < N; id++) { + // std::find on all_items + boolean found = false; + for (int existing : allItems) { + ops++; + if (existing == id) { found = true; break; } + } + if (!found) { + allItems.add(id); + } + } + return ops; + } + + static long allItemsResizeFixed(int N) { + LinkedList allItems = new LinkedList<>(); + Set allItemsSet = new HashSet<>(); + allItems.add(0); + allItemsSet.add(0); + long ops = 0; + for (int id = 1; id < N; id++) { + ops++; + if (!allItemsSet.contains(id)) { + allItems.add(id); + allItemsSet.add(id); + } + } + return ops; + } + + public static void main(String[] args) { + int pass = 0, fail = 0; + + // Test kdenlive-0001: ThumbnailCache (10 clips x 200 frames) + { + long defectOps = thumbnailCacheDefect(10, 200); + long fixedOps = thumbnailCacheFixed(10, 200); + double ratio = (double) defectOps / fixedOps; + boolean ok = ratio > 5.0 && fixedOps < defectOps; + System.out.printf("kdenlive-0001 ThumbnailCache storedOnDisk: defect=%d fixed=%d ratio=%.1fx %s%n", + defectOps, fixedOps, ratio, ok ? "PASS" : "FAIL"); + if (ok) pass++; else fail++; + } + + // Test kdenlive-0002: clipIds mix (N=500) + { + long defectOps = clipIdsMixDefect(500); + long fixedOps = clipIdsMixFixed(500); + double ratio = (double) defectOps / fixedOps; + boolean ok = ratio > 50.0; + System.out.printf("kdenlive-0002 clipIds mix membership: defect=%d fixed=%d ratio=%.1fx %s%n", + defectOps, fixedOps, ratio, ok ? "PASS" : "FAIL"); + if (ok) pass++; else fail++; + } + + // Test kdenlive-0003: sorted_clips move (N=500) + { + long defectOps = sortedClipsMoveDefect(500); + long fixedOps = sortedClipsMoveFixed(500); + double ratio = (double) defectOps / fixedOps; + boolean ok = ratio > 50.0; + System.out.printf("kdenlive-0003 sorted_clips moveGroup: defect=%d fixed=%d ratio=%.1fx %s%n", + defectOps, fixedOps, ratio, ok ? "PASS" : "FAIL"); + if (ok) pass++; else fail++; + } + + // Test kdenlive-0004: all_items resize (N=500) + { + long defectOps = allItemsResizeDefect(500); + long fixedOps = allItemsResizeFixed(500); + double ratio = (double) defectOps / fixedOps; + boolean ok = ratio > 50.0; + System.out.printf("kdenlive-0004 all_items resize dedup: defect=%d fixed=%d ratio=%.1fx %s%n", + defectOps, fixedOps, ratio, ok ? "PASS" : "FAIL"); + if (ok) pass++; else fail++; + } + + System.out.printf("%nSummary: %d/%d PASS%n", pass, pass + fail); + if (fail > 0) System.exit(1); + } +} diff --git a/defects/shotcut/patch/shotcut-0001-playlist-hashes-linear-find.patch b/defects/shotcut/patch/shotcut-0001-playlist-hashes-linear-find.patch new file mode 100644 index 000000000..6019b8aed --- /dev/null +++ b/defects/shotcut/patch/shotcut-0001-playlist-hashes-linear-find.patch @@ -0,0 +1,64 @@ +# UNDF: (leave blank) +# Defect: shotcut-0001 +# Component: src/docks/playlistdock.cpp — PlaylistProxyModel m_hashes +# Pattern: CWE-407 — std::find on vector m_hashes inside filterAcceptsRow (called per row) +# Severity: MEDIUM — O(N^2) where N = playlist clips; each row calls std::find on m_hashes +# Fix: Change m_hashes from vector to unordered_set for O(1) lookup +--- a/src/docks/playlistdock.cpp ++++ b/src/docks/playlistdock.cpp +@@ -149,7 +149,7 @@ + class ProducerHashesParser : public Mlt::Parser + { + private: +- std::vector m_hashes; ++ std::unordered_set m_hashes; + + public: + ProducerHashesParser() +@@ -159,9 +159,7 @@ +- std::vector &hashes() ++ std::unordered_set &hashes() + { +- std::sort(m_hashes.begin(), m_hashes.end()); +- std::set unique(m_hashes.begin(), m_hashes.end()); +- std::copy(unique.begin(), unique.end(), std::back_inserter(m_hashes)); ++ // unordered_set is already deduplicated + return m_hashes; + } + +@@ -168,7 +166,7 @@ + int on_start_producer(Mlt::Producer *producer) + { + if (producer->is_cut()) +- m_hashes.push_back(Util::getHash(producer->parent()).toStdString()); ++ m_hashes.insert(Util::getHash(producer->parent()).toStdString()); + return 0; + } + +@@ -204,7 +202,7 @@ + auto hash = Util::getHash(clip->parent()).toStdString(); +- return std::find(m_hashes.begin(), m_hashes.end(), hash) != m_hashes.end(); ++ return m_hashes.count(hash) > 0; + +@@ -229,7 +227,7 @@ + auto hash = Util::getHash(clip->parent()).toStdString(); +- return std::find(m_hashes.begin(), m_hashes.end(), hash) == m_hashes.end(); ++ return m_hashes.count(hash) == 0; + +@@ -265,8 +263,8 @@ +- m_hashes.clear(); +- std::set_difference(hashes.begin(), ... std::back_inserter(m_hashes)); ++ m_hashes.clear(); ++ // For duplicates: insert hashes that appear more than once ++ std::sort(hashes.begin(), hashes.end()); ++ for (size_t i = 1; i < hashes.size(); i++) { ++ if (hashes[i] == hashes[i-1]) m_hashes.insert(hashes[i]); ++ } + +@@ -278,7 +276,7 @@ +- m_hashes = parser.hashes(); ++ m_hashes = parser.hashes(); // now returns unordered_set + +@@ -328,1 +326,1 @@ +- std::vector m_hashes; ++ std::unordered_set m_hashes; diff --git a/defects/shotcut/unit/ShotcutTest.class b/defects/shotcut/unit/ShotcutTest.class new file mode 100644 index 000000000..b649aad7d Binary files /dev/null and b/defects/shotcut/unit/ShotcutTest.class differ diff --git a/defects/shotcut/unit/ShotcutTest.java b/defects/shotcut/unit/ShotcutTest.java new file mode 100644 index 000000000..5e44bd5b5 --- /dev/null +++ b/defects/shotcut/unit/ShotcutTest.java @@ -0,0 +1,61 @@ +import java.util.*; + +/** + * CWE-407 simulation for Shotcut defects. + * shotcut-0001: PlaylistProxyModel m_hashes vector std::find in filterAcceptsRow + */ +public class ShotcutTest { + + // --- shotcut-0001: m_hashes linear find per playlist row --- + + static long playlistHashesDefect(int N) { + // Build m_hashes vector with N/2 unique hashes (simulating timeline clips) + List hashes = new ArrayList<>(); + for (int i = 0; i < N / 2; i++) { + hashes.add("hash-" + i); + } + long ops = 0; + // filterAcceptsRow called for each of N playlist rows + for (int row = 0; row < N; row++) { + String hash = "hash-" + (row % N); + // std::find linear scan + for (String h : hashes) { + ops++; + if (h.equals(hash)) break; + } + } + return ops; + } + + static long playlistHashesFixed(int N) { + Set hashes = new HashSet<>(); + for (int i = 0; i < N / 2; i++) { + hashes.add("hash-" + i); + } + long ops = 0; + for (int row = 0; row < N; row++) { + ops++; + String hash = "hash-" + (row % N); + hashes.contains(hash); + } + return ops; + } + + public static void main(String[] args) { + int pass = 0, fail = 0; + + // Test shotcut-0001: playlist N=1000 + { + long defectOps = playlistHashesDefect(1000); + long fixedOps = playlistHashesFixed(1000); + double ratio = (double) defectOps / fixedOps; + boolean ok = ratio > 50.0; + System.out.printf("shotcut-0001 m_hashes filterAcceptsRow: defect=%d fixed=%d ratio=%.1fx %s%n", + defectOps, fixedOps, ratio, ok ? "PASS" : "FAIL"); + if (ok) pass++; else fail++; + } + + System.out.printf("%nSummary: %d/%d PASS%n", pass, pass + fail); + if (fail > 0) System.exit(1); + } +}