Batch 9 (15): bun, bzflag (3), cake_wallet (4), calligra, caprice32 (2), cataclysm (3), cemu Batch 10 (15): cemu-0002, citra, clickhouse-java, cmake (3), cocos2d (3), conduit, cura (2), curaengine, clamav, contiki
2.8 KiB
Caprice32 — CWE-407 Disclosure Brief (caprice32-0001)
2026-04-14 · Patch available — awaiting upstream merge
Finding
One O(n) defect in Caprice32's Z80 emulation core. The breakpoint hit-test in the main CPU execution loop uses std::any_of over a std::vector<Breakpoint> on every instruction cycle. Fix adds an std::unordered_set<dword> shadow index for O(1) address lookup.
The Defect
caprice32-0001 (PATCHED — HIGH): src/z80.cpp:1098
// In z80_execute() — fires on EVERY Z80 instruction:
if (!breakpoints.empty()) {
if ((z80.breakpoint_reached = std::any_of(breakpoints.begin(), breakpoints.end(),
[&](const auto& b) { return b.address == _PC; }))) break;
}
z80_execute() runs the main Z80 CPU loop. On every instruction cycle, if any breakpoints exist, the code scans the entire breakpoint vector comparing each breakpoint's address against the current program counter. The Z80 executes millions of instructions per second at emulated speed.
Complexity Proof
At B=10 breakpoints:
- Defective: up to 10 comparisons per Z80 instruction (millions of times per second)
- Fixed: 1 hash lookup per Z80 instruction
- ~10× op reduction on the hottest path in the emulator. Fires at MHz frequency.
Impact
Caprice32 emulates the Amstrad CPC. Developers debugging CPC software set breakpoints in the Z80 disassembler. Even a small number of breakpoints adds measurable overhead to emulation because the check fires on every single instruction. Users debugging complex programs with many breakpoints experience visible emulation slowdown.
The Fix
Add std::unordered_set<dword> breakpoint_addresses maintained alongside the breakpoint vector:
// Before
std::any_of(breakpoints.begin(), breakpoints.end(),
[&](const auto& b) { return b.address == _PC; })
// After
// CWE-407 fix: unordered_set for O(1) breakpoint address lookup.
std::unordered_set<dword> breakpoint_addresses;
// In z80_execute():
if ((z80.breakpoint_reached = (breakpoint_addresses.count(_PC) != 0))) break;
Patch
Fix available: defects/caprice32-0001/patch/caprice32-0001.patch
Single-file patch on src/z80.cpp. Adds breakpoint_addresses set with add_breakpoint(), remove_breakpoint(), and remove_breakpoints_if() helper functions to keep the set in sync with the vector.
What We Ask
A patch is ready for review.
- Confirm receipt and assign a GitHub issue reference (ColinPitrat/caprice32).
- Assess severity — fires on every emulated Z80 instruction when breakpoints exist.
- Coordinate a disclosure date — we target 90 days from first contact.
- We will credit the Caprice32 team in the public disclosure. Preferred acknowledgment format welcome.
Contact: see cover email. This brief is confidential until coordinated disclosure.