From c24246e2e24ee9fb193881143f12b21a26e3bdcc Mon Sep 17 00:00:00 2001 From: "russell@unturf.com" Date: Mon, 13 Apr 2026 14:03:16 -0400 Subject: [PATCH] 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 --- .../mastodon/unit/test_mastodon_cwe1333.rb | 123 ++++++++++++++ whitepaper/outreach/blender.md | 102 ++++++++++++ whitepaper/outreach/cpython.md | 145 +++++++++++++++++ whitepaper/outreach/kdenlive.md | 154 ++++++++++++++++++ whitepaper/outreach/libreoffice.md | 116 +++++++++++++ whitepaper/outreach/maven.md | 136 ++++++++++++++++ 6 files changed, 776 insertions(+) create mode 100644 defects/mastodon/unit/test_mastodon_cwe1333.rb create mode 100644 whitepaper/outreach/blender.md create mode 100644 whitepaper/outreach/cpython.md create mode 100644 whitepaper/outreach/kdenlive.md create mode 100644 whitepaper/outreach/libreoffice.md create mode 100644 whitepaper/outreach/maven.md diff --git a/defects/mastodon/unit/test_mastodon_cwe1333.rb b/defects/mastodon/unit/test_mastodon_cwe1333.rb new file mode 100644 index 000000000..fba3d19de --- /dev/null +++ b/defects/mastodon/unit/test_mastodon_cwe1333.rb @@ -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 diff --git a/whitepaper/outreach/blender.md b/whitepaper/outreach/blender.md new file mode 100644 index 000000000..94b0df918 --- /dev/null +++ b/whitepaper/outreach/blender.md @@ -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.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 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 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` for O(1) dedup of used joint indices; maintain ordered `Vector` for downstream use. + +**blender-0003:** Add `std::unordered_set visited_set` alongside `visited_files` vector for O(1) membership checks. + +**blender-0004:** Build `blender::Set 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. diff --git a/whitepaper/outreach/cpython.md b/whitepaper/outreach/cpython.md new file mode 100644 index 000000000..9313e3d15 --- /dev/null +++ b/whitepaper/outreach/cpython.md @@ -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. diff --git a/whitepaper/outreach/kdenlive.md b/whitepaper/outreach/kdenlive.md new file mode 100644 index 000000000..2ba0d8244 --- /dev/null +++ b/whitepaper/outreach/kdenlive.md @@ -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 with std::find for dedup +std::unordered_map> m_storedVolatile; +mutable std::unordered_map> m_storedOnDisk; +// std::find() is O(V) per lookup — fires in getThumbnail, storeThumbnail, saveCachedThumbs +``` + +`m_storedOnDisk` and `m_storedVolatile` use `vector` 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 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 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 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 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 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::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` 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` with `unordered_set` for `m_storedOnDisk` and `m_storedVolatile`. + +**kdenlive-0002/0003/0004/0006:** Build `unordered_set` from the vector/list before the loop for O(1) membership. + +**kdenlive-0005:** Add shadow `QSet` alongside `QVariantList` for O(1) `.contains()`. + +**kdenlive-0007:** Add `QHash m_rowIndex` shadow map for O(1) name-to-row lookup. + +**kdenlive-0008:** Build `QSet` 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` 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. diff --git a/whitepaper/outreach/libreoffice.md b/whitepaper/outreach/libreoffice.md new file mode 100644 index 000000000..281b8fe99 --- /dev/null +++ b/whitepaper/outreach/libreoffice.md @@ -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` 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` before the slot registration loop for O(1) group dedup. + +**libreoffice-0004:** Pass `unordered_set` alongside the vector to `InsertLine()` for O(1) dedup. + +**libreoffice-0005:** Build `unordered_set` 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. diff --git a/whitepaper/outreach/maven.md b/whitepaper/outreach/maven.md new file mode 100644 index 000000000..af20cc6d2 --- /dev/null +++ b/whitepaper/outreach/maven.md @@ -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 children = new ArrayList<>(); +final List 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` 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 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` 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 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` 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 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 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 STANDARD_LIFECYCLE_IDS` from `DefaultLifecycles.STANDARD_LIFECYCLES`. + +**maven-0003:** Add `Map cycleIndexMap` parameter to `visitCycle()` for O(1) label lookup during cycle detection. + +**maven-0004:** Add `buildOrderMap()` helper returning `IdentityHashMap`, used in three sort sites. + +**maven-0005:** Build `IdentityHashMap` from `plan.sortedNodes()` before the stream sort. + +**maven-0006:** Replace `ArrayList` with `HashSet` for `blackList`. + +**maven-0007:** Replace `ArrayList` with `LinkedHashSet` 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.