java-topology/whitepaper/outreach/dry.md

4.7 KiB
Raw Blame History

Dry Engine (Urho3D fork) — CWE-407 Disclosure Brief

2026-03-27 · Patch available — awaiting upstream merge

Finding

Two O(n²) defects in Dry's UI selection system and event subscription system. Both patched. Patches ready for upstream review. Dry is a C++ game engine forked from Urho3D.

The Defects

dry-0001 (PATCHED — CRITICAL): Source/Dry/UI/ListView.cpp:529,556

// In ListView::SetSelections() — fires on every multi-selection change:
// First loop — check each current selection against incoming:
for (unsigned i = 0; i < selections_.Size(); i++) {
    if (!indices.Contains(selections_[i])) { ... }  // PODVector<unsigned>::Contains() O(n)
}
// Second loop — check each incoming against current:
for (unsigned i = 0; i < indices.Size(); i++) {
    if (!selections_.Contains(indices[i])) { ... }  // PODVector<unsigned>::Contains() O(n)
}

Two back-to-back O(n²) loops. Both PODVector<unsigned>::Contains() calls are linear scans. Fires on every UI multi-selection change: drag-select, keyboard range-select, programmatic selection update.

dry-0002 (PATCHED — HIGH): Source/Dry/Core/Object.cpp:278

// In Object::UnsubscribeFromAllEventsExcept() — fires on object teardown:
for (EventHandlerMap::Iterator i = eventHandlers_.Begin(); i != eventHandlers_.End(); i++) {
    if (!exceptions.Contains(i->first_))  // PODVector<StringHash>::Contains() O(m)
        // unsubscribe from this event
}
// O(n × m) total: n=handler count, m=exceptions size

exceptions is PODVector<StringHash>. Contains() is O(m) per handler. O(n × m) total on object teardown — common during scene transitions, level unload, object pooling.

Complexity Proof

dry-0001: For k selection indices (current and incoming):

  • First loop: k × O(k) checks
  • Second loop: k × O(k) checks
  • Total: O(k²) — two simultaneous O(n²) passes

At k=2,000: defective=3,125,750 comparisons (approx k²/2 × 2), fixed=3,500 (HashSet shadows). 893× op reduction.

dry-0002: For n handlers and m exceptions:

  • Per handler: O(m) Contains() scan
  • Total: O(n × m)

At n=100 handlers, m=5 exceptions: defective=500 comparisons, fixed=100 (HashSet lookup). 48× op reduction.

Impact

Dry/Urho3D is a C++ game engine used for mobile games, desktop applications, and embedded interactive software. It is particularly used in educational contexts and resource-constrained deployments where a lightweight C++ engine is needed.

dry-0001 fires on every multi-selection event in the UI system. For list views with thousands of items (file browsers, entity editors, level editors with many objects), selection changes trigger quadratic work. Editor tools built on Dry that allow multi-select operations are worst-case.

dry-0002 fires on every object teardown that calls UnsubscribeFromAllEventsExcept(). This is common during scene unloading, object pooling (returning objects to pool), and level transitions. In games that pool many objects, this fires frequently.

The Fix

dry-0001: Build HashSet<unsigned> shadows before both loops:

// Before
if (!indices.Contains(selections_[i])) { ... }
if (!selections_.Contains(indices[i])) { ... }

// After
// CWE-407 fix: HashSet shadows for O(1) Contains() instead of O(n) PODVector scans.
HashSet<unsigned> indicesSet(indices.Begin(), indices.End());
HashSet<unsigned> selectionsSet(selections_.Begin(), selections_.End());
// Use indicesSet.Contains() and selectionsSet.Contains() in respective loops

dry-0002: Build HashSet<StringHash> from exceptions once at function entry:

// Before
if (!exceptions.Contains(i->first_)) { ... }

// After
// CWE-407 fix: HashSet built once for O(1) Contains() instead of O(m) PODVector scan.
HashSet<StringHash> excSet(exceptions.Begin(), exceptions.End());
if (!excSet.Contains(i->first_)) { ... }

StringHash implements operator== and is suitable as a hash set key (it wraps a 32-bit hash value).

Patch

Fix available: defects/dry/patch/dry-0001-0002-listview-hashset.patch

Two-location patch across UI/ListView.cpp and Core/Object.cpp.

Unit test: DryEngineTest 4/4 pass. dry-0001: 893× speedup at k=2,000. dry-0002: 48× speedup at n=100, m=5.

What We Ask

A patch is ready for review.

  1. Confirm receipt and assign a GitHub issue reference (Urho3D/Urho3D or the Dry repository).
  2. Assess severity — dry-0001 fires on every multi-selection UI event; dry-0002 fires on every object teardown using exception-based unsubscription.
  3. Coordinate a disclosure date — we are targeting 90 days from first contact.
  4. We will credit the Dry/Urho3D team in the public disclosure. Preferred acknowledgment format welcome.

Contact: see cover email. This brief is confidential until coordinated disclosure.