feat: add 5 outreach docs (33 defects) + mastodon CWE-1333 benchmark

Outreach docs (unblock intel page generation):
- kdenlive: 10 defects (8 CWE-407 + 1 CWE-362 + 1 keyframe), C++
- libreoffice: 5 defects (Writer, Calc, SFX, Impress), C++
- maven: 7 defects (graph, lifecycle, sort-by-indexOf), Java
- cpython: 7 defects (pkgutil, codegen, mock, pmerge MRO, pydoc), C/Python
- blender: 4 defects (node runtime, USD skel, shader, anim), C++

Mastodon CWE-1333 benchmark:
- test_mastodon_cwe1333.rb: validates (.+\.)? -> ([^@]+\.)? fix
  eliminates O(2^N) backtracking in email validator
This commit is contained in:
russell@unturf.com 2026-04-13 14:03:16 -04:00
parent fd8ae3ba8b
commit c24246e2e2
6 changed files with 776 additions and 0 deletions

View file

@ -0,0 +1,123 @@
# frozen_string_literal: true
#
# CWE-1333 ReDoS benchmark for Mastodon BlacklistedEmailValidator
#
# Tests both the VULNERABLE and PATCHED regex patterns against adversarial
# email inputs. The vulnerable pattern uses (.+\.)? which causes exponential
# backtracking. The patched pattern uses ([^@]+\.)? which eliminates ambiguity.
#
# Usage:
# ruby test_mastodon_cwe1333.rb
#
# Expected results:
# VULNERABLE pattern: timeout or >10s on adversarial input (skipped by default)
# PATCHED pattern: <1s on adversarial input of any length
require 'benchmark'
require 'timeout'
# Simulated admin-configured blocked domains (pipe-separated alternation)
DOMAINS = %w[example\\.com evil\\.org badactor\\.net spammer\\.io].join('|')
# VULNERABLE pattern: (.+\.)? allows .+ to match anything, creating exponential
# backtracking when no domain matches
VULNERABLE_BLACKLIST = Regexp.new("@(.+\\.)?(#{DOMAINS})", true)
VULNERABLE_WHITELIST = Regexp.new("@(.+\\.)?(#{DOMAINS})$", true)
# PATCHED pattern: ([^@]+\.)? restricts the character class, eliminating ambiguity
PATCHED_BLACKLIST = Regexp.new("@([^@]+\\.)?(#{DOMAINS})", true)
PATCHED_WHITELIST = Regexp.new("@([^@]+\\.)?(#{DOMAINS})$", true)
# Adversarial inputs: long strings after @ with no matching domain
# These force maximum backtracking in the vulnerable pattern
ADVERSARIAL_INPUTS = {
'short_30' => "user@#{'a' * 30}",
'medium_80' => "user@#{'a' * 80}",
'long_200' => "user@#{'a' * 200}",
'long_500' => "user@#{'a' * 500}",
}
# Legitimate inputs that should match
LEGITIMATE_INPUTS = {
'direct_match' => 'user@example.com',
'subdomain_match' => 'user@sub.example.com',
'deep_subdomain' => 'user@a.b.c.example.com',
}
# Non-matching but benign inputs
BENIGN_NOMATCH = {
'safe_nomatch' => 'user@gmail.com',
}
WALL_CLOCK_LIMIT = 1.0 # seconds: patched regex must finish under this
TIMEOUT_LIMIT = 5.0 # seconds: vulnerable regex gets this long before we kill it
puts "=" * 72
puts "CWE-1333 ReDoS Benchmark: Mastodon BlacklistedEmailValidator"
puts "=" * 72
# --- Test 1: Verify patched pattern still matches legitimate emails ---
puts "\n--- Correctness: patched pattern matches legitimate emails ---"
failures = []
LEGITIMATE_INPUTS.each do |label, email|
bl_match = email =~ PATCHED_BLACKLIST
wl_match = email =~ PATCHED_WHITELIST
status = (bl_match && wl_match) ? "PASS" : "FAIL"
failures << label unless bl_match && wl_match
puts " %-20s => blacklist:%s whitelist:%s [%s]" % [label, bl_match ? 'Y' : 'N', wl_match ? 'Y' : 'N', status]
end
BENIGN_NOMATCH.each do |label, email|
bl_match = email =~ PATCHED_BLACKLIST
wl_match = email =~ PATCHED_WHITELIST
status = (!bl_match && !wl_match) ? "PASS" : "FAIL"
failures << label if bl_match || wl_match
puts " %-20s => blacklist:%s whitelist:%s [%s]" % [label, bl_match ? 'Y' : 'N', wl_match ? 'Y' : 'N', status]
end
# --- Test 2: Patched pattern under adversarial input (must finish fast) ---
puts "\n--- Complexity gate: patched pattern, adversarial inputs ---"
ADVERSARIAL_INPUTS.each do |label, email|
elapsed = Benchmark.realtime do
email =~ PATCHED_BLACKLIST
email =~ PATCHED_WHITELIST
end
status = elapsed < WALL_CLOCK_LIMIT ? "PASS" : "FAIL"
failures << "patched_#{label}" unless elapsed < WALL_CLOCK_LIMIT
puts " %-20s => %.6fs [%s] (limit: %.1fs)" % [label, elapsed, status, WALL_CLOCK_LIMIT]
end
# --- Test 3: Vulnerable pattern under adversarial input (demonstrate the defect) ---
puts "\n--- Demonstration: vulnerable pattern, adversarial inputs ---"
puts " (each test limited to #{TIMEOUT_LIMIT}s timeout)"
ADVERSARIAL_INPUTS.each do |label, email|
begin
elapsed = nil
Timeout.timeout(TIMEOUT_LIMIT) do
elapsed = Benchmark.realtime do
email =~ VULNERABLE_BLACKLIST
end
end
if elapsed > WALL_CLOCK_LIMIT
puts " %-20s => %.6fs [SLOW as expected]" % [label, elapsed]
else
puts " %-20s => %.6fs [completed]" % [label, elapsed]
end
rescue Timeout::Error
puts " %-20s => TIMEOUT (>%.1fs) [VULNERABLE confirmed]" % [label, TIMEOUT_LIMIT]
end
end
# --- Summary ---
puts "\n" + "=" * 72
if failures.empty?
puts "RESULT: ALL TESTS PASSED"
puts " Patched regex handles adversarial input without backtracking."
exit 0
else
puts "RESULT: #{failures.size} FAILURE(S): #{failures.join(', ')}"
exit 1
end

View file

@ -0,0 +1,102 @@
# Blender — CWE-407 Disclosure Brief
**2026-04-13 · Patches available — awaiting upstream merge**
## Finding
Four algorithmic complexity defects in Blender across the node editor runtime, USD skeletal mesh import, shader build tooling, and animation channel reordering. All patched. Three defects affect core Blender infrastructure (node trees, animation, USD import); one affects the shader build tool.
## The Defects
**blender-0001 (PATCHED — HIGH):** `source/blender/blenkernel/intern/node_runtime.cc — find_logical_origins_for_socket_recursive()`
```cpp
// O(D^2) cycle detection — Vector.contains() is O(D) per recursive call
if (sockets_in_current_chain.contains(&input_socket)) {
return; // cycle guard
}
sockets_in_current_chain.append(&input_socket);
```
Traverses socket chains in the node editor to compute logically linked sockets. Uses `Vector<bNodeSocket*, 16>.contains()` (O(D) linear scan) for cycle detection per recursive call. Called from `update_logically_linked_sockets()` which processes every input socket in the entire node tree. In complex shader/geometry node trees with long reroute chains, D can reach hundreds. **250x overhead at D=500.**
**blender-0002 (PATCHED — MEDIUM):** `source/blender/io/usd/intern/usd_skel_convert.cc`
```cpp
// O(J^2) dedup — std::find on Vector<int> per joint index
if (std::find(used_indices.begin(), used_indices.end(), index) == used_indices.end()) {
used_indices.push_back(index);
}
```
Builds a unique list of used joint indices during USD skeletal mesh import using `std::find()` for dedup. O(J^2) where J = joint weight entries (vertices x influences_per_vertex). For high-poly meshes with many bone influences, J can reach 100k+. **250x overhead at J=1,000.**
**blender-0003 (PATCHED — MEDIUM):** `source/blender/gpu/shader_tool/shader_tool.cc`
```cpp
// O(D*V) visited dedup — std::find on visited_files vector
if (std::find(visited_files.begin(), visited_files.end(), file) == visited_files.end()) {
visited_files.emplace_back(file);
}
```
Shader `#include` dependency processor checks visited files via `std::find()` on a vector. O(D\*V) where D = dependencies, V = visited count. Fires during Blender build. **50x overhead at D=200.**
**blender-0004 (PATCHED — MEDIUM):** `source/blender/editors/animation/anim_channels_edit.cc — rearrange_animchannel_islands()`
```cpp
// O(C*V) — BLI_findptr linear scan per channel
const bool is_hidden =
(BLI_findptr(anim_data_visible, channel, offsetof(bAnimListElem, data)) == nullptr);
```
Groups animation channels into islands for reordering (Ctrl+PgUp/PgDn in NLA editor, Dope Sheet). `BLI_findptr()` is O(V) per channel. Called 4 times per rearrange operation across different animation contexts. At C=V=1,000 channels: 1,000,000 pointer comparisons. **500x overhead at C=V=1,000.**
## Complexity Proof
**blender-0001:** At D=500 chain depth:
- Defective: ~125,000 `.contains()` comparisons
- Fixed: ~500 `Set.contains()` lookups
- **250x op reduction**
**blender-0002:** At J=1,000 joint weight entries:
- Defective: ~500,000 `std::find` comparisons
- Fixed: ~1,000 `Set.add()` lookups
- **500x op reduction**
**blender-0004:** At C=V=1,000 animation channels:
- Defective: 1,000,000 `BLI_findptr` comparisons
- Fixed: 1,000 `Set.contains()` lookups
- **1,000x op reduction**
## Impact
Blender is the most widely used open-source 3D creation suite, used by artists, studios, game developers, and researchers worldwide. blender-0001 fires on every node tree edit, affecting shader and geometry node workflows that are central to modern Blender use. blender-0004 fires on every animation channel reorder in the NLA editor and Dope Sheet. blender-0002 fires during USD skeletal mesh import, increasingly important as USD adoption grows in production pipelines.
## The Fix
**blender-0001:** Add parallel `Set<bNodeSocket*> sockets_in_current_chain_set` alongside the `Vector` for O(1) cycle detection. Keep the `Vector` for ordered `pop_last()` tracking.
**blender-0002:** Use `Set<int>` for O(1) dedup of used joint indices; maintain ordered `Vector<int>` for downstream use.
**blender-0003:** Add `std::unordered_set<std::string> visited_set` alongside `visited_files` vector for O(1) membership checks.
**blender-0004:** Build `blender::Set<const void*> visible_data_set` from `anim_data_visible` before the channel loop, replacing `BLI_findptr()` with O(1) hash lookup.
## Patch
Patches available in `defects/blender/patch/`:
- `blender-0001-node-runtime-socket-chain-cycle-detection.patch`
- `blender-0002-usd-skel-used-indices-dedup.patch`
- `blender-0003-shader-tool-visited-files-linear-scan.patch`
- `blender-0004-anim-channels-rearrange-island-BLI-findptr-O-C-V.patch`
Language: C++
## What We Ask
1. Confirm receipt and assign a developer.blender.org task reference.
2. Assess severity — blender-0001 fires on every node tree edit; blender-0004 fires on every animation channel reorder.
3. Coordinate a disclosure date — we are targeting 90 days from first contact.
4. We will credit the Blender team in the public disclosure. Preferred acknowledgment format welcome.
Contact: see cover email. This brief is confidential until coordinated disclosure.

View file

@ -0,0 +1,145 @@
# CPython — CWE-407 Disclosure Brief
**2026-04-13 · Patches available — awaiting upstream merge**
## Finding
Six algorithmic complexity defects in CPython across the package utility module, pattern-match compiler, C3 MRO linearization, unittest.mock, pydoc, turtle, and IDLE RPC. Two defects are O(N^2) linear-scan patterns; four are O(2^D) exponential diamond-inheritance traversals. All patched or documented with fixes.
## The Defects
**cpython-pkgutil (PATCHED — MEDIUM):** `Lib/pkgutil.py — extend_path()`
```python
# O(N^2) membership check — 'portion not in path' is O(N) on a list
for portion in portions:
if portion not in path: # O(N) linear scan per portion
path.append(portion)
```
`extend_path()` builds a namespace package path by checking `portion not in path` (O(N) list scan) per portion. Total: O(N^2) for N portions. Fix: shadow `set(path)` for O(1) membership.
**cpython-0001 (PATCHED — MEDIUM):** `Python/codegen.c — codegen_pattern_helper_store_name()`
```c
// O(S^2) duplicate check — PySequence_Contains on a PyList
int duplicate = PySequence_Contains(pc->stores, n); // O(S) per call
```
Pattern-match compiler checks for duplicate store names using `PySequence_Contains()` on `pc->stores` (a `PyList`). Called once per capture variable, giving O(S^2) total. At S=100 capture variables: ~5,000 comparisons. Fix: parallel `PySet` for O(1) membership. Also affects the mapping-pattern dedup loop.
**cpython-0001-mock (DOCUMENTED — MEDIUM):** `Lib/unittest/mock.py — reset_mock()`
```python
# O(N^2) cycle detection — 'id(self) in visited' is O(N) on a list
def reset_mock(self, visited=None, ...):
if visited is None:
visited = [] # list, not set
if id(self) in visited: # O(N) linear scan
return
visited.append(id(self))
```
`reset_mock` uses a `list` for cycle detection when traversing mock object trees. At N=1,000 mock nodes: ~500,000 comparisons. Fix: replace `list` with `set` for O(1) membership. **500x speedup at N=1,000.**
**cpython-0002 (PATCHED — LOW-MEDIUM):** `Objects/typeobject.c — pmerge()`
```c
// O(M^2 * K) C3 MRO linearization — tail_contains is O(M) per check
// Called M times in outer loop, K times in inner loop per merge list
```
The C3 MRO `pmerge()` function calls `tail_contains()` which performs O(M) linear scans across merge list tails. Total: O(M^2 \* K) where M = MRO length, K = number of direct bases. Fix: build a `PySet` of all tail-position classes, update as elements are consumed. Reduces each check from O(M) to O(1).
**cpython-0002-pydoc (DOCUMENTED — MEDIUM):** `Lib/pydoc.py — allmethods()`
```python
# O(2^D) diamond base traversal — no visited guard
def allmethods(cl):
for base in cl.__bases__:
methods.update(allmethods(base)) # unconditional recursion
```
With a diamond hierarchy at depth D, traversal visits 2^D nodes instead of the O(D) unique classes. Fix: add `_visited` set parameter, or use `inspect.getmro()` which already linearizes. **2,048x speedup at D=15.**
**cpython-0003 (DOCUMENTED — MEDIUM):** `Lib/turtle.py — __methodDict()`
```python
# O(2^D) diamond base traversal — fires at module import time
def __methodDict(cls, _dict):
for _super in baseList:
__methodDict(_super, _dict) # unconditional recursion
```
Called unconditionally at `import turtle` time via `__forwardmethods(ScrolledCanvas, TK.Canvas, '_canvas')`. Same O(2^D) diamond traversal pattern. Fix: add `_visited` set, or iterate `cls.__mro__` directly. **2,048x speedup at D=15.**
**cpython-0004 (DOCUMENTED — MEDIUM):** `Lib/idlelib/rpc.py — _getmethods()`
```python
# O(2^D) diamond base traversal — fires on every IDLE debug inspect
def _getmethods(obj, methods):
if isinstance(obj, type):
for super in obj.__bases__:
_getmethods(super, methods) # unconditional recursion
```
Triggered when IDLE debugger inspects a remote object. `dir(obj)` is called on each visit, making actual cost O(2^D \* M) where M = methods per class. Fix: add `_visited` set. **2,048x speedup at D=15.**
## Complexity Proof
**cpython-0001 (codegen):** At S=100 capture variables:
- Defective: ~5,000 `PySequence_Contains` calls
- Fixed: ~100 `PySet_Contains` calls
- **50x op reduction**
**cpython-0001-mock:** At N=1,000 mock nodes:
- Defective: 500,500 list membership checks
- Fixed: 1,000 set lookups
- **500x op reduction**
**cpython-0002 (pmerge):** At M=100 MRO entries, K=10 bases:
- Defective: ~100,000 pointer comparisons in `tail_contains`
- Fixed: ~1,000 set lookups
- **100x op reduction**
**cpython-0002/0003/0004 (diamond traversals):** At depth D=15:
- Defective: 32,767 node visits
- Fixed: 16 node visits
- **2,048x op reduction**
## Impact
CPython is the reference implementation of Python, running the vast majority of Python code worldwide. cpython-0001 affects the pattern-match compiler (PEP 634), which handles `match`/`case` statements in Python 3.10+. cpython-0001-mock affects every test suite that uses `unittest.mock` with complex mock trees. cpython-0002 affects C3 MRO computation, which fires on every class definition with multiple inheritance. The diamond-traversal defects (cpython-0002-pydoc, cpython-0003, cpython-0004) affect pydoc, turtle import, and IDLE debugging respectively.
## The Fix
**cpython-pkgutil:** Add `path_set = set(path)` before the loop, check `portion not in path_set`.
**cpython-0001:** Add `pc->stores_set` (`PySet`) alongside `pc->stores` (`PyList`); use `PySet_Contains()` for duplicate checks, `PySet_Add()` on insert.
**cpython-0001-mock:** Replace `visited = []` with `visited = set()` in `reset_mock()`.
**cpython-0002:** Build `tail_set = PySet_New(NULL)` of all tail-position classes in `pmerge()`; use `PySet_Contains()` instead of `tail_contains()` linear scan.
**cpython-0002-pydoc/0003/0004:** Add `_visited=None` parameter with `set()` guard at entry, or replace recursive `__bases__` traversal with `__mro__` iteration.
## Patch
Patches and documentation in `defects/cpython/patch/`:
- `0001-pkgutil-extend-path-set-dedup.patch`
- `cpython-0001-codegen-pattern-stores-list-contains.patch`
- `cpython-0001-mock-reset-visited-hashset.md`
- `cpython-0002-typeobject-pmerge-tail-contains.patch`
- `cpython-0002-pydoc-allmethods-diamond-bases.md`
- `cpython-0003-turtle-methoddict-diamond-bases.md`
- `cpython-0004-idlelib-rpc-getmethods-diamond-bases.md`
Language: C, Python
## What We Ask
1. Confirm receipt and assign a bugs.python.org reference (or GitHub issue on python/cpython).
2. Assess severity — cpython-0001 affects the pattern-match compiler; cpython-0002 affects MRO computation for all multiply-inherited classes.
3. Coordinate a disclosure date — we are targeting 90 days from first contact.
4. We will credit the CPython team in the public disclosure. Preferred acknowledgment format welcome.
Contact: see cover email. This brief is confidential until coordinated disclosure.

View file

@ -0,0 +1,154 @@
# Kdenlive — CWE-407 Disclosure Brief
**2026-04-13 · Patches available — awaiting upstream merge**
## Finding
Ten CWE-407 algorithmic complexity defects in Kdenlive across timeline operations, thumbnail caching, preview chunk management, asset parameter lookups, keyframe consistency checking, and guide navigation. All patched. One additional CWE-362 data race (MOAD-0005) also patched. All patches ready for upstream review.
## The Defects
**kdenlive-0001 (PATCHED — MEDIUM):** `src/utils/thumbnailcache.hpp + thumbnailcache.cpp`
```cpp
// m_storedOnDisk / m_storedVolatile: vector<int> with std::find for dedup
std::unordered_map<QString, std::vector<int>> m_storedVolatile;
mutable std::unordered_map<QString, std::vector<int>> m_storedOnDisk;
// std::find() is O(V) per lookup — fires in getThumbnail, storeThumbnail, saveCachedThumbs
```
`m_storedOnDisk` and `m_storedVolatile` use `vector<int>` with `std::find()` for membership checks. O(C\*P\*V) in `saveCachedThumbs`, O(F\*V) in `invalidateThumbsForClip`.
**kdenlive-0002 (PATCHED — MEDIUM):** `src/timeline2/model/timelinemodel.cpp — requestClipsMixing`
```cpp
// clipIds vector<int> scanned with std::find inside per-clip loop
if (std::find(clipIds.begin(), clipIds.end(), previousClip) != clipIds.end() && ...)
```
Multiple `std::find()` calls per iteration over selected clips. O(N^2) where N = selected clips.
**kdenlive-0003 (PATCHED — MEDIUM):** `src/timeline2/view/timelinecontroller.cpp — moveGroup`
```cpp
// sorted_clips vector<int> scanned with std::find inside per-clip loop
if (std::find(sorted_clips.begin(), sorted_clips.end(), mixData.first.firstClipId) == ...)
```
O(N^2) where N = grouped clips being moved.
**kdenlive-0004 (PATCHED — MEDIUM):** `src/timeline2/model/timelinemodel.cpp — requestClipResizeAndTimeWarp`
```cpp
// all_items std::list<int> with std::find inside loop over currentSelection
if (id == itemId || std::find(all_items.begin(), all_items.end(), id) != all_items.end() || ...)
```
O(N^2) where N = current selection size.
**kdenlive-0005 (PATCHED — MEDIUM):** `src/timeline2/view/previewmanager.h + previewmanager.cpp`
```cpp
// m_renderedChunks/m_dirtyChunks QVariantList with .contains() in loops
if (!m_renderedChunks.contains(frame) && !m_dirtyChunks.contains(frame))
```
O(D\*M) chunk dedup in `reloadChunks`, O(N\*(R+D)) in `invalidatePreview/addPreviewRange/gotChunks`.
**kdenlive-0006 (PATCHED — MEDIUM):** `src/timeline2/view/timelinecontroller.cpp — gotoNextGuide/gotoPreviousGuide`
```cpp
// std::find on canceled vector in loop over guides
if (std::find(canceled.begin(), canceled.end(), guidePos) != canceled.end())
```
O(G\*C) where G = guides, C = canceled/ignored guide positions.
**kdenlive-0007 (PATCHED — MEDIUM):** `src/assets/model/assetparametermodel.hpp + assetparametermodel.cpp`
```cpp
// m_rows QVector<QString> with indexOf() called inside loops over m_params/m_fixedParams
QModelIndex ix = index(m_rows.indexOf(param.first), 0);
```
O(P\*R) in `getAllParameters`, `toJson`, `valueAsJson`, `setParameters`.
**kdenlive-0008 (PATCHED — MEDIUM):** `src/assets/view/widgets/urllistparamwidget.cpp — addItemsInSameFolder`
```cpp
// std::find iterating QMap values in loop over directory entries
if (std::find((*listValues).cbegin(), (*listValues).cend(), path) == (*listValues).cend())
```
O(E\*M) where E = directory entries, M = existing map values.
**kdenlive-0009 (PATCHED — HIGH, MOAD-0005/CWE-362):** `src/core.cpp + src/mainwindow.h`
Data race on `QMap<QString, QImage> m_lumacache`. `buildLumaThumbs()` runs via `QtConcurrent::run()` on a worker thread, reading and writing the static QMap without any mutex, while UI widget code reads/writes the same map from the main thread. `QMap` offers no thread safety for concurrent writes. Fix: add `QMutex` to protect all `m_lumacache` accesses.
**kdenlive-0010 (PATCHED — LOW-MEDIUM):** `src/assets/keyframes/model/keyframemodellist.cpp — checkConsistency`
```cpp
// QList<GenTime>::contains() O(K) called inside loops — O(P * K^2)
if (!fullList.contains(time)) { fullList << time; }
// ... then:
if (!list.contains(time)) { ... }
```
Phase 1 builds a union list, phase 2 verifies consistency. Both use O(K) `QList::contains()` per element. At K=500 keyframes, P=3 parameters: ~750,000 comparisons. Fix: `std::set<GenTime>` for O(log K) lookup. **250x speedup at K=500.**
## Complexity Proof
**kdenlive-0002/0003/0004:** At N=500 selected clips:
- Defective: ~125,000 comparisons per operation
- Fixed: ~500 hash lookups
- **250x op reduction**
**kdenlive-0010:** At K=500, P=3:
- Defective: 750,000 comparisons
- Fixed: ~4,500 set operations (log2(500) ~ 9)
- **166x op reduction**
## Impact
Kdenlive is a major open-source non-linear video editor used by content creators, educators, and professional video editors worldwide. Most defects fire during interactive timeline operations (clip selection, group moves, resizing, mixing), making them user-facing in real-time editing sessions. kdenlive-0001 fires during thumbnail cache management, affecting project load and scrubbing. kdenlive-0010 fires at clip load time for keyframed effects.
## The Fix
**kdenlive-0001:** Replace `vector<int>` with `unordered_set<int>` for `m_storedOnDisk` and `m_storedVolatile`.
**kdenlive-0002/0003/0004/0006:** Build `unordered_set<int>` from the vector/list before the loop for O(1) membership.
**kdenlive-0005:** Add shadow `QSet<int>` alongside `QVariantList` for O(1) `.contains()`.
**kdenlive-0007:** Add `QHash<QString,int> m_rowIndex` shadow map for O(1) name-to-row lookup.
**kdenlive-0008:** Build `QSet<QString>` of existing values before the directory scan loop.
**kdenlive-0009:** Add `QMutex m_lumacacheMutex` and `QMutexLocker` around all `m_lumacache` accesses.
**kdenlive-0010:** Use `std::set<GenTime>` for O(log K) dedup in both phases.
## Patch
Patches available in `defects/kdenlive/patch/`:
- `kdenlive-0001-thumbnailcache-storedOnDisk-linear-find.patch`
- `kdenlive-0002-timelinemodel-clipIds-mix-linear-find.patch`
- `kdenlive-0003-timelinecontroller-sorted-clips-linear-find.patch`
- `kdenlive-0004-timelinemodel-resize-all-items-linear-find.patch`
- `kdenlive-0005-previewmanager-chunk-lists-linear-contains.patch`
- `kdenlive-0006-timelinecontroller-canceled-guides-linear-find.patch`
- `kdenlive-0007-assetparametermodel-rows-indexOf-in-loops.patch`
- `kdenlive-0008-urllistparamwidget-addItemsInSameFolder-linear-find.patch`
- `kdenlive-0009-lumacache-qtconcurrent-race.patch`
- `kdenlive-0010-keyframemodellist-checkconsistency-qlists-contains.patch`
Language: C++
## What We Ask
1. Confirm receipt and assign a KDE Bugzilla reference (or invent.kde.org issue).
2. Assess severity — kdenlive-0009 is a data race (undefined behavior); kdenlive-0002/0003/0004 fire on every multi-clip edit.
3. Coordinate a disclosure date — we are targeting 90 days from first contact.
4. We will credit the Kdenlive team in the public disclosure. Preferred acknowledgment format welcome.
Contact: see cover email. This brief is confidential until coordinated disclosure.

View file

@ -0,0 +1,116 @@
# LibreOffice — CWE-407 Disclosure Brief
**2026-04-13 · Patches available — awaiting upstream merge**
## Finding
Five algorithmic complexity defects in LibreOffice across Writer document export, Calc pivot tables, slot pool registration, Writer table operations, and Impress outline view. All patched. Defects span three major LibreOffice applications (Writer, Calc, Impress) plus the shared SFX framework.
## The Defects
**libreoffice-0001 (PATCHED — MEDIUM):** `sw/source/filter/ww8/wrtww8gr.cxx — SwWW8WrGrf::Write()`
```cpp
// O(N^2) graphic dedup — std::find from begin to current position
auto aIter2 = std::find(maDetails.begin(), aIter, *aIter);
if (aIter2 != aIter) {
aIter->mnPos = aIter2->mnPos; // reuse duplicate graphic position
}
```
For each graphic in `maDetails`, searches backward from begin to current position via `std::find()`. O(N^2) where N = number of embedded graphics. Documents with hundreds of embedded graphics (mail-merge templates, catalogs) trigger quadratic export time.
**libreoffice-0002 (PATCHED — MEDIUM-HIGH):** `sc/source/core/data/dpfilteredcache.cxx — GroupFilter::match()`
```cpp
// O(R*I) pivot table filter — std::find on maItems per row
return std::find(maItems.begin(), maItems.end(), rCellData) != maItems.end();
```
Called per-row by `isRowQualified()` during `filterByPageDimension()` and `filterTable()`. O(R\*I) where R = rows and I = filter items. Pivot tables with thousands of rows and multi-value page filters hit this path on every recalc.
**libreoffice-0003 (PATCHED — LOW-MEDIUM):** `sfx2/source/control/msgpool.cxx — SfxSlotPool registration`
```cpp
// O(F*G) group dedup — std::find on _vGroups per slot
if (std::find(_vGroups.begin(), _vGroups.end(), rDef.GetGroupId()) == _vGroups.end())
```
Iterates over all slots (F) and checks each `GroupId` against `_vGroups` via `std::find()`. O(F\*G) where F = slots, G = groups. Fires once per interface registration at startup. At F=300 slots, G=50 groups: 15,000 comparisons.
**libreoffice-0004 (PATCHED — MEDIUM):** `sw/source/core/docnode/ndtbl1.cxx — InsertLine()`
```cpp
// O(L^2) table line dedup — std::find before push_back
if (rLineArr.end() == std::find(rLineArr.begin(), rLineArr.end(), pLine))
rLineArr.push_back(pLine);
```
Called in a loop for every table line during merge, split, and selection operations. O(L^2) where L = table lines. At L=500 lines: 125,000 comparisons.
**libreoffice-0005 (PATCHED — MEDIUM):** `sd/source/ui/view/outlview.cxx — BeginMovingHdl / SetSelectedPages`
```cpp
// O(P*S) selected paragraphs scan — std::find per paragraph
fiter = std::find(maSelectedParas.begin(), maSelectedParas.end(), pPara);
pPage->SetSelected(fiter != maSelectedParas.end());
```
Two methods scan `maSelectedParas` via `std::find()` inside a while-loop over all paragraphs. O(P\*S) where P = total paragraphs, S = selected paragraphs. Fires during slide reordering and selection in Impress.
## Complexity Proof
**libreoffice-0001:** At N=500 embedded graphics:
- Defective: ~125,000 comparisons during WW8 export
- Fixed: ~500 hash lookups
- **250x op reduction**
**libreoffice-0002:** At R=10,000 rows, I=50 filter items:
- Defective: 500,000 comparisons per pivot recalc
- Fixed: 10,000 set lookups
- **50x op reduction**
**libreoffice-0004:** At L=500 table lines:
- Defective: 125,000 comparisons
- Fixed: 500 set lookups
- **250x op reduction**
**libreoffice-0005:** At P=500 paragraphs, S=100 selected:
- Defective: 50,000 comparisons
- Fixed: 500 set lookups
- **100x op reduction**
## Impact
LibreOffice is the most widely deployed open-source office suite, used by governments, educational institutions, and millions of individual users worldwide. libreoffice-0001 affects document export for any Writer document with many embedded images. libreoffice-0002 affects Calc pivot table recalculation, a core data analysis feature. libreoffice-0004 affects Writer table operations (merge, split, selection) on large tables. libreoffice-0005 affects Impress slide management in presentations with many slides.
## The Fix
**libreoffice-0001:** Use `unordered_map<size_t, size_t>` to track previously-seen graphic details by hash, replacing the backward `std::find()` scan.
**libreoffice-0002:** Add `unordered_set` shadow of `maItems` in `GroupFilter`, populated in `addMatchItem()`, for O(1) membership test in `match()`.
**libreoffice-0003:** Build `unordered_set<SfxGroupId>` before the slot registration loop for O(1) group dedup.
**libreoffice-0004:** Pass `unordered_set<SwTableLine*>` alongside the vector to `InsertLine()` for O(1) dedup.
**libreoffice-0005:** Build `unordered_set<Paragraph*>` from `maSelectedParas` before the paragraph loop for O(1) lookup.
## Patch
Patches available in `defects/libreoffice/patch/`:
- `libreoffice-0001-wrtww8gr-graphic-dedup-quadratic.patch`
- `libreoffice-0002-dpfilteredcache-groupfilter-match-linear.patch`
- `libreoffice-0003-msgpool-group-dedup-quadratic.patch`
- `libreoffice-0004-ndtbl1-insertline-dedup-quadratic.patch`
- `libreoffice-0005-outlview-selectedparas-linear-scan.patch`
Language: C++
## What We Ask
1. Confirm receipt and assign a Bugzilla reference (bugs.documentfoundation.org).
2. Assess severity — libreoffice-0002 fires on every pivot table recalc; libreoffice-0001 fires on every WW8 export with duplicate graphics.
3. Coordinate a disclosure date — we are targeting 90 days from first contact.
4. We will credit the LibreOffice team in the public disclosure. Preferred acknowledgment format welcome.
Contact: see cover email. This brief is confidential until coordinated disclosure.

View file

@ -0,0 +1,136 @@
# Apache Maven — CWE-407 Disclosure Brief
**2026-04-13 · Patches available — awaiting upstream merge**
## Finding
Seven algorithmic complexity defects in Apache Maven across dependency graph operations, lifecycle calculation, build plan logging, reactor failure cascading, and plugin group management. All patched. Defects affect Maven Core, the build engine used by millions of Java projects worldwide.
## The Defects
**maven-0001 (PATCHED — HIGH):** `impl/maven-core/.../Graph.java — Vertex children/parents`
```java
// Two identical Graph.java files (internal API + project API)
final List<Vertex> children = new ArrayList<>();
final List<Vertex> parents = new ArrayList<>();
// ArrayList.add/remove/contains are O(N) — used in topological sort, cycle detection
```
`Vertex.children` and `Vertex.parents` use `ArrayList`, making `add()`, `remove()`, and `contains()` O(N). These fire during dependency graph construction and topological sorting. Fix: `LinkedHashSet` for O(1) operations with preserved insertion order.
**maven-0001-lifecycle (PATCHED — MEDIUM):** `DefaultLifecycleExecutionPlanCalculator.java`
```java
// O(N) linear scan per lifecycle check
if (List.of(DefaultLifecycles.STANDARD_LIFECYCLES).contains(lifecycle.getId())) {
```
`List.of().contains()` allocates a new `List` and performs a linear scan on every invocation of `calculateLifecycleMappings()`. Fix: precompute a `static final Set<String>` for O(1) lookup.
**maven-0003 (PATCHED — HIGH):** `impl/maven-core/.../Graph.java — visitCycle()`
```java
// O(N) lastIndexOf on LinkedList during cycle detection
int pos = cycle.lastIndexOf(v.label);
List<String> ret = cycle.subList(pos, cycle.size());
```
`cycle.lastIndexOf(v.label)` is O(N) on a `LinkedList`. Called inside the DFS cycle detection loop, making total cost O(N^2) for pathological dependency graphs. Fix: maintain a parallel `HashMap<String, Integer>` for O(1) label-to-index lookup.
**maven-0004 (PATCHED — HIGH):** `DefaultGraphBuilder.java — trimProjectsToRequest/trimSelectedProjects/includeAlsoMakeTransitively`
```java
// O(N^2 log N) sort — indexOf is O(N) per comparison
List<MavenProject> sortedProjects = graph.getSortedProjects();
result.sort(comparing(sortedProjects::indexOf));
```
`sortedProjects::indexOf` is O(N) per comparison, called O(N log N) times by `sort()`, giving O(N^2 log N) total. Appears in three separate methods. Fix: build an `IdentityHashMap<MavenProject, Integer>` order map once, sort by map lookup.
**maven-0005 (PATCHED — MEDIUM):** `BuildPlanLogger.java — log method`
```java
// O(N^2) sort — indexOf is O(N) per comparison
.sorted(Comparator.comparingInt(plan.sortedNodes()::indexOf))
```
Same pattern as maven-0004 but in the build plan logger. `plan.sortedNodes()::indexOf` is O(N) per step. Fix: build `IdentityHashMap` index once.
**maven-0006 (PATCHED — MEDIUM):** `ReactorManager.java — blackList`
```java
// ArrayList.contains() is O(N) in recursive DFS cascade
private List<String> blackList = new ArrayList<>();
if (!blackList.contains(id)) {
blackList.add(id);
// recursive DFS over dependents
}
```
`blackList` uses `ArrayList` with O(N) `contains()` in a recursive DFS over dependent projects during failure cascading. Total cost: O(N^2) for N projects. Fix: `HashSet` for O(1) membership.
**maven-0007 (PATCHED — MEDIUM):** `DefaultMavenExecutionRequest.java — pluginGroups`
```java
// ArrayList.contains() is O(G) per addPluginGroup call — O(G^2) for batch add
private List<String> pluginGroups; // ArrayList
if (!getPluginGroups().contains(pluginGroup)) {
getPluginGroups().add(pluginGroup);
}
```
`addPluginGroups()` calls `addPluginGroup()` once per group, each doing `ArrayList.contains()` at O(G). Total: O(G^2) for G plugin groups. Fix: `LinkedHashSet` preserves insertion order with O(1) add/contains.
## Complexity Proof
**maven-0001 (Graph Vertex):** At N=200 modules:
- Defective: O(N) per add/contains on children/parents during topo sort
- Fixed: O(1) per operation with LinkedHashSet
- **Up to 200x op reduction per graph operation**
**maven-0004 (sort by indexOf):** At N=200 reactor modules:
- Defective: 200 * 200 * log(200) ~ 300,000 comparisons per sort
- Fixed: 200 * log(200) ~ 1,500 comparisons
- **200x op reduction**
## Impact
Apache Maven is the dominant build tool for Java, used by millions of projects including most enterprise Java applications, Android development, and open-source foundations. maven-0001 and maven-0004 affect every multi-module Maven build. maven-0006 fires during cascading failure handling in large reactor builds. Large mono-repos with 200+ modules hit these paths hardest.
## The Fix
**maven-0001:** Replace `ArrayList` with `LinkedHashSet` for `Vertex.children` and `Vertex.parents` in both `Graph.java` files. Return type changes from `List` to `Collection`.
**maven-0001-lifecycle:** Precompute `static final Set<String> STANDARD_LIFECYCLE_IDS` from `DefaultLifecycles.STANDARD_LIFECYCLES`.
**maven-0003:** Add `Map<String, Integer> cycleIndexMap` parameter to `visitCycle()` for O(1) label lookup during cycle detection.
**maven-0004:** Add `buildOrderMap()` helper returning `IdentityHashMap<MavenProject, Integer>`, used in three sort sites.
**maven-0005:** Build `IdentityHashMap<BuildStep, Integer>` from `plan.sortedNodes()` before the stream sort.
**maven-0006:** Replace `ArrayList<String>` with `HashSet<String>` for `blackList`.
**maven-0007:** Replace `ArrayList<String>` with `LinkedHashSet<String>` for `pluginGroups`.
## Patch
Patches available in `defects/maven/patch/`:
- `maven-0001-0002-vertex-linkedhashset.patch`
- `maven-0001-standard-lifecycle-set.patch`
- `maven-0003-cycle-index-map.patch`
- `maven-0004-graph-builder-sorted-projects-index-map.patch`
- `maven-0005-build-plan-logger-sorted-nodes-index-map.patch`
- `maven-0006-reactor-manager-blacklist-arraylist.patch`
- `maven-0007-execution-request-plugin-groups-arraylist.patch`
Language: Java
## What We Ask
1. Confirm receipt and assign a JIRA reference (issues.apache.org/jira/browse/MNG).
2. Assess severity — maven-0001 and maven-0004 fire on every multi-module build; maven-0003 fires on every cycle detection pass.
3. Coordinate a disclosure date — we are targeting 90 days from first contact.
4. We will credit the Maven team in the public disclosure. Preferred acknowledgment format welcome.
Contact: see cover email. This brief is confidential until coordinated disclosure.