java-topology/whitepaper/outreach/0ad-0001.md
russell@unturf.com 9a78d1afbe feat: add 15 outreach docs (15 defects) for 1-patch projects
0ad (4), aranym, ardour, argo-cd, aria2, azahar, bcoin, bind9,
btcpayserver (3), bullet3. Mix of CWE-407 and CWE-312.
2026-04-14 14:33:17 -04:00

3.2 KiB
Raw Blame History

0 A.D. — CWE-407 Disclosure Brief (0ad-0001)

2026-04-13 · Patch available — awaiting upstream merge

Finding

One O(n²) defect in 0 A.D.'s obstruction manager. The dirty shape tracking system uses std::vector with std::find for deduplication, causing O(N*D) per-frame cost where N = nearby shapes and D = dirty list size. In large battles (200v200), hundreds of shapes move per frame, making this quadratic.

The Defect

0ad-0001 (PATCHED — HIGH): source/simulation2/components/CCmpObstructionManager.cpp:525

// In CCmpObstructionManager — dirty shape dedup fires per frame:
std::vector<u32> m_DirtyStaticShapes;
std::vector<u32> m_DirtyUnitShapes;

// Every MakeDirtyStatic/MakeDirtyUnit call does:
if (std::find(m_DirtyStaticShapes.begin(), m_DirtyStaticShapes.end(), index)
    == m_DirtyStaticShapes.end())
    m_DirtyStaticShapes.push_back(index);

m_DirtyStaticShapes and m_DirtyUnitShapes accumulate during each frame. Every shape movement triggers MakeDirtyStatic or MakeDirtyUnit, which scans the growing vector with std::find before appending. The RasterizeHelper step also does per-shape membership checks against these vectors. In large battles with hundreds of moving units, the dirty lists grow large and every insertion plus every rasterize check pays O(D).

Complexity Proof

At D=400 dirty shapes per frame (200v200 battle):

  • Defective: 400 insertions × average 200 comparisons = ~80,000 comparisons per frame, plus rasterize checks
  • Fixed: 400 insertions × O(1) = 400 hash lookups per frame
  • ~200× op reduction per frame. Fires every simulation frame.

Impact

0 A.D. is a popular open-source real-time strategy game with large-scale battles as a core gameplay feature. The defect fires every simulation frame during combat. Players running large battles (200v200 or larger) experience frame drops as the dirty shape dedup cost grows quadratically with battle size.

The Fix

Replace std::vector<u32> with std::unordered_set<u32> for both dirty shape lists:

// Before
std::vector<u32> m_DirtyStaticShapes;
std::vector<u32> m_DirtyUnitShapes;
if (std::find(m_DirtyStaticShapes.begin(), m_DirtyStaticShapes.end(), index)
    == m_DirtyStaticShapes.end())
    m_DirtyStaticShapes.push_back(index);

// After
// CWE-407 fix: unordered_set for O(1) membership test instead of O(D) vector scan.
std::unordered_set<u32> m_DirtyStaticShapes;
std::unordered_set<u32> m_DirtyUnitShapes;
m_DirtyStaticShapes.insert(index);

Patch

Fix available: defects/0ad-0001/patch/0ad-0001.patch

Single-file patch across CCmpObstructionManager.cpp. Replaces all std::find + push_back patterns with std::unordered_set::insert, and updates rasterize membership checks to use set::find.

What We Ask

A patch is ready for review.

  1. Confirm receipt and assign an issue reference (0ad/0ad or Wildfire Games Trac).
  2. Assess severity — this defect fires every simulation frame during battles.
  3. Coordinate a disclosure date — we target 90 days from first contact.
  4. We will credit the 0 A.D. team in the public disclosure. Preferred acknowledgment format welcome.

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