From 28af16f96cba6cf37146319394a692b81b37b8ce Mon Sep 17 00:00:00 2001 From: "russell@unturf.com" Date: Tue, 31 Mar 2026 19:12:58 -0400 Subject: [PATCH] caprice32: 2 CWE-407 defects, MOAD 0002-0005 CLEAN --- .../caprice32-0001/patch/caprice32-0001.patch | 39 +++ .../caprice32-0001/test/test_caprice32_0001 | Bin 0 -> 23976 bytes .../test/test_caprice32_0001.cpp | 194 +++++++++++++++ .../caprice32-0002/patch/caprice32-0002.patch | 57 +++++ .../caprice32-0002/test/test_caprice32_0002 | Bin 0 -> 23704 bytes .../test/test_caprice32_0002.cpp | 225 ++++++++++++++++++ 6 files changed, 515 insertions(+) create mode 100644 defects/caprice32-0001/patch/caprice32-0001.patch create mode 100755 defects/caprice32-0001/test/test_caprice32_0001 create mode 100644 defects/caprice32-0001/test/test_caprice32_0001.cpp create mode 100644 defects/caprice32-0002/patch/caprice32-0002.patch create mode 100755 defects/caprice32-0002/test/test_caprice32_0002 create mode 100644 defects/caprice32-0002/test/test_caprice32_0002.cpp diff --git a/defects/caprice32-0001/patch/caprice32-0001.patch b/defects/caprice32-0001/patch/caprice32-0001.patch new file mode 100644 index 000000000..0100822d5 --- /dev/null +++ b/defects/caprice32-0001/patch/caprice32-0001.patch @@ -0,0 +1,39 @@ +# UNDF: UNDF-2026-XXXXXXXXX +--- a/src/z80.cpp ++++ b/src/z80.cpp +@@ -160,6 +160,7 @@ static void* const ed_jumpTable[256] = { + #include + #include ++#include + + t_z80regs z80; + std::vector breakpoints; ++// Fast O(1) address lookup set kept in sync with breakpoints vector. ++std::unordered_set breakpoint_addresses; + std::vector watchpoints; +@@ -164,16 +165,24 @@ std::vector watchpoints; + // All callers that mutate breakpoints must also mutate breakpoint_addresses. + // API helpers to keep them in sync: ++void add_breakpoint(Breakpoint bp) { ++ breakpoints.push_back(bp); ++ breakpoint_addresses.insert(bp.address); ++} ++ ++void remove_breakpoint(std::size_t idx) { ++ breakpoint_addresses.erase(breakpoints[idx].address); ++ breakpoints.erase(breakpoints.begin() + idx); ++} ++ ++void remove_breakpoints_if(std::function pred) { ++ for (const auto& bp : breakpoints) { ++ if (pred(bp)) breakpoint_addresses.erase(bp.address); ++ } ++ breakpoints.erase(std::remove_if(breakpoints.begin(), breakpoints.end(), pred), breakpoints.end()); ++} +--- a/src/z80.cpp ++++ b/src/z80.cpp +@@ -1098,7 +1098,7 @@ dword z80_execute(dword iCycleCountInit) + if (!breakpoints.empty()) { +- if ((z80.breakpoint_reached = std::any_of(breakpoints.begin(), breakpoints.end(), [&](const auto& b) { return b.address == _PC; }))) break; ++ if ((z80.breakpoint_reached = (breakpoint_addresses.count(_PC) != 0))) break; + } diff --git a/defects/caprice32-0001/test/test_caprice32_0001 b/defects/caprice32-0001/test/test_caprice32_0001 new file mode 100755 index 0000000000000000000000000000000000000000..23a229fac9bb17ddb15ec911a08d2431344c7f1b GIT binary patch literal 23976 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 literal 0 HcmV?d00001 diff --git a/defects/caprice32-0001/test/test_caprice32_0001.cpp b/defects/caprice32-0001/test/test_caprice32_0001.cpp new file mode 100644 index 000000000..a52322831 --- /dev/null +++ b/defects/caprice32-0001/test/test_caprice32_0001.cpp @@ -0,0 +1,194 @@ +// Unit test for caprice32-0001: CWE-407 breakpoint linear scan O(B) per Z80 instruction +// +// In src/z80.cpp the inner emulation loop checks breakpoints via: +// std::any_of(breakpoints.begin(), breakpoints.end(), [&](const auto& b) { return b.address == _PC; }) +// This is O(B) per instruction. With B breakpoints loaded and the PC never hitting any, +// every instruction scans all B entries. At ~4 MHz Z80 clock, that is ~4 million O(B) +// scans per second -- a multiplier of B applied to our emulation speed. +// +// Fix: maintain a parallel std::unordered_set breakpoint_addresses kept in sync +// with the breakpoints vector. Inner loop becomes: +// breakpoint_addresses.count(_PC) != 0 +// which is O(1) average. +// +// Compile: g++ -std=c++17 -O2 -o test_caprice32_0001 test_caprice32_0001.cpp +// Run: ./test_caprice32_0001 + +#include +#include +#include +#include +#include +#include +#include + +typedef uint32_t dword; +typedef uint16_t word; + +enum BreakpointType { NORMAL = 0, EPHEMERAL = 1 }; + +struct Breakpoint { + Breakpoint(word val, BreakpointType t = NORMAL) : address(val), type(t) {} + dword address; + BreakpointType type; +}; + +// --- DEFECT: O(B) std::any_of scan per instruction --- +struct DefectLoop { + std::vector breakpoints; + + void add(Breakpoint bp) { breakpoints.push_back(bp); } + + // Returns true if hit; scanned_out = number of entries examined. + bool check(word pc, int& scanned_out) { + scanned_out = 0; + for (const auto& b : breakpoints) { + ++scanned_out; + if (b.address == pc) return true; + } + return false; + } +}; + +// --- FIX: O(1) unordered_set lookup --- +struct FixedLoop { + std::vector breakpoints; + std::unordered_set breakpoint_addresses; + + void add(Breakpoint bp) { + breakpoints.push_back(bp); + breakpoint_addresses.insert(bp.address); + } + + void remove_if(std::function pred) { + for (const auto& bp : breakpoints) { + if (pred(bp)) breakpoint_addresses.erase(bp.address); + } + breakpoints.erase( + std::remove_if(breakpoints.begin(), breakpoints.end(), pred), + breakpoints.end()); + } + + void remove_at(std::size_t idx) { + breakpoint_addresses.erase(breakpoints[idx].address); + breakpoints.erase(breakpoints.begin() + idx); + } + + // Returns true if hit; scanned_out = 1 (hash probe) or 0 (fast no-match). + bool check(word pc, int& scanned_out) { + if (breakpoints.empty()) { scanned_out = 0; return false; } + bool hit = (breakpoint_addresses.count(pc) != 0); + scanned_out = 1; // one hash probe regardless of B + return hit; + } +}; + +static void test_defect_scans_all_on_miss() { + const int B = 500; + DefectLoop d; + for (int i = 0; i < B; ++i) + d.add(Breakpoint(word(0x1000 + i))); + + int scanned = 0; + bool hit = d.check(0x0000, scanned); + + assert(!hit); + assert(scanned == B && "defect: all B entries scanned for a miss"); + printf("PASS defect: %d breakpoints, PC miss -> scanned %d entries\n", B, scanned); +} + +static void test_fix_constant_work_on_miss() { + const int B = 500; + FixedLoop f; + for (int i = 0; i < B; ++i) + f.add(Breakpoint(word(0x1000 + i))); + + int scanned = 0; + bool hit = f.check(0x0000, scanned); + + assert(!hit); + assert(scanned <= 1 && "fix: O(1) probe regardless of B"); + printf("PASS fix: %d breakpoints, PC miss -> scanned %d entries (O(1))\n", B, scanned); +} + +static void test_fix_detects_hit() { + FixedLoop f; + f.add(Breakpoint(0x0400)); + f.add(Breakpoint(0x0800)); + f.add(Breakpoint(0x0C00)); + + int scanned = 0; + bool hit = f.check(0x0800, scanned); + + assert(hit && "fix: should detect breakpoint at 0x0800"); + printf("PASS fix: breakpoint at 0x0800 detected in %d probe(s)\n", scanned); +} + +static void test_fix_remove_at_updates_set() { + FixedLoop f; + f.add(Breakpoint(0x0300)); + f.add(Breakpoint(0x0600)); + + // Remove first breakpoint (index 0, address 0x0300). + f.remove_at(0); + + int scanned = 0; + bool hit = f.check(0x0300, scanned); + assert(!hit && "fix: removed breakpoint should not fire"); + printf("PASS fix: after remove, 0x0300 no longer hits\n"); + + hit = f.check(0x0600, scanned); + assert(hit && "fix: remaining breakpoint should still fire"); + printf("PASS fix: remaining breakpoint 0x0600 still hits\n"); +} + +static void test_fix_ephemeral_remove() { + FixedLoop f; + f.add(Breakpoint(0x0200, EPHEMERAL)); + f.add(Breakpoint(0x0400, NORMAL)); + + // Remove all ephemeral breakpoints (simulating RemoveEphemeralBreakpoints). + f.remove_if([](const Breakpoint& bp) { return bp.type == EPHEMERAL; }); + + int scanned = 0; + bool hit = f.check(0x0200, scanned); + assert(!hit && "fix: ephemeral breakpoint removed from set"); + + hit = f.check(0x0400, scanned); + assert(hit && "fix: normal breakpoint still present"); + printf("PASS fix: ephemeral removal leaves normal breakpoint intact\n"); +} + +static void test_speedup_ratio() { + const int B = 500; + DefectLoop d; + FixedLoop f; + for (int i = 0; i < B; ++i) { + word addr = word(0x8000 + i); + d.add(Breakpoint(addr)); + f.add(Breakpoint(addr)); + } + + int defect_scanned = 0, fix_scanned = 0; + d.check(0x0000, defect_scanned); + f.check(0x0000, fix_scanned); + + // Observed speedup ratio per-check. + int ratio = defect_scanned; // fix_scanned is 1, so ratio ~= defect_scanned + printf("PASS speedup: defect=%d scans/check, fix=%d scan/check, ratio ~%dx\n", + defect_scanned, fix_scanned, ratio); + assert(defect_scanned == B); + assert(fix_scanned <= 1); +} + +int main() { + printf("--- caprice32-0001 CWE-407 breakpoint O(B) scan per Z80 instruction ---\n"); + test_defect_scans_all_on_miss(); + test_fix_constant_work_on_miss(); + test_fix_detects_hit(); + test_fix_remove_at_updates_set(); + test_fix_ephemeral_remove(); + test_speedup_ratio(); + printf("ALL PASS\n"); + return 0; +} diff --git a/defects/caprice32-0002/patch/caprice32-0002.patch b/defects/caprice32-0002/patch/caprice32-0002.patch new file mode 100644 index 000000000..4fe626ea8 --- /dev/null +++ b/defects/caprice32-0002/patch/caprice32-0002.patch @@ -0,0 +1,57 @@ +# UNDF: UNDF-2026-XXXXXXXXX +--- a/src/z80.cpp ++++ b/src/z80.cpp +@@ -162,6 +162,9 @@ t_z80regs z80; + std::vector breakpoints; + std::unordered_set breakpoint_addresses; + std::vector watchpoints; ++// Fast O(1) lookup sets for watchpoints, separated by access type. ++std::unordered_set watchpoint_reads; // addresses watched on READ ++std::unordered_set watchpoint_writes; // addresses watched on WRITE +--- a/src/z80.cpp ++++ b/src/z80.cpp +@@ -340,11 +340,11 @@ inline byte read_mem(word addr) { + if (!watchpoints.empty()) { +- if (std::any_of(watchpoints.begin(), watchpoints.end(), [&](const auto& w) { +- return w.address == addr && (w.type & READ); +- })) { ++ if (watchpoint_reads.count(addr)) { + z80.watchpoint_reached = 1; + } + } + return read_mem_no_watchpoint(addr); + } +@@ -355,11 +355,11 @@ inline void write_mem(word addr, byte val) { + if (!watchpoints.empty()) { +- if (std::any_of(watchpoints.begin(), watchpoints.end(), [&](const auto& w) { +- return w.address == addr && (w.type & WRITE); +- })) { ++ if (watchpoint_writes.count(addr)) { + z80.watchpoint_reached = 1; + } + } +--- a/src/gui/src/CapriceDevTools.cpp ++++ b/src/gui/src/CapriceDevTools.cpp +@@ -1353,9 +1353,19 @@ void CapriceDevTools::AddWatchpoint(word address, WatchpointType type) { +- watchpoints.emplace_back(address, type); ++ watchpoints.emplace_back(address, type); ++ if (type & READ) watchpoint_reads.insert(address); ++ if (type & WRITE) watchpoint_writes.insert(address); + } + + void CapriceDevTools::RemoveWatchpoint(std::size_t idx) { ++ const auto& wp = watchpoints[idx]; ++ // Only erase from sets if no other watchpoint covers this address+type. ++ bool other_read = false; ++ bool other_write = false; ++ for (std::size_t i = 0; i < watchpoints.size(); i++) { ++ if (i == idx) continue; ++ if (watchpoints[i].address == wp.address) { ++ if (watchpoints[i].type & READ) other_read = true; ++ if (watchpoints[i].type & WRITE) other_write = true; ++ } ++ } ++ if (!other_read && (wp.type & READ)) watchpoint_reads.erase(wp.address); ++ if (!other_write && (wp.type & WRITE)) watchpoint_writes.erase(wp.address); + watchpoints.erase(watchpoints.begin() + idx); + } diff --git a/defects/caprice32-0002/test/test_caprice32_0002 b/defects/caprice32-0002/test/test_caprice32_0002 new file mode 100755 index 0000000000000000000000000000000000000000..e99316bf9a0f29909d4c723fd2f1055321cfaeba GIT binary patch literal 23704 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 t@Gtd|?ZS3+L3hr -- watchpoint_reads and +// watchpoint_writes -- kept in sync with the watchpoints vector. +// Inner loop becomes: watchpoint_reads.count(addr) or watchpoint_writes.count(addr) +// which is O(1) average. +// +// Compile: g++ -std=c++17 -O2 -o test_caprice32_0002 test_caprice32_0002.cpp +// Run: ./test_caprice32_0002 + +#include +#include +#include +#include +#include + +typedef uint32_t dword; +typedef uint16_t word; + +enum WatchpointType { READ = 1, WRITE = 2, READWRITE = 3 }; + +struct Watchpoint { + Watchpoint(word val, WatchpointType t) : address(val), type(t) {} + dword address; + WatchpointType type; +}; + +// --- DEFECT: O(W) std::any_of per memory access --- +struct DefectMemory { + std::vector watchpoints; + int last_read_scanned = 0; + int last_write_scanned = 0; + + void add(Watchpoint wp) { watchpoints.push_back(wp); } + + bool read_mem(word addr) { + last_read_scanned = 0; + if (!watchpoints.empty()) { + for (const auto& w : watchpoints) { + ++last_read_scanned; + if (w.address == addr && (w.type & READ)) return true; + } + } + return false; + } + + bool write_mem(word addr) { + last_write_scanned = 0; + if (!watchpoints.empty()) { + for (const auto& w : watchpoints) { + ++last_write_scanned; + if (w.address == addr && (w.type & WRITE)) return true; + } + } + return false; + } +}; + +// --- FIX: O(1) unordered_set lookup --- +struct FixedMemory { + std::vector watchpoints; + std::unordered_set watchpoint_reads; + std::unordered_set watchpoint_writes; + int last_read_scanned = 0; + int last_write_scanned = 0; + + void add(Watchpoint wp) { + watchpoints.push_back(wp); + if (wp.type & READ) watchpoint_reads.insert(wp.address); + if (wp.type & WRITE) watchpoint_writes.insert(wp.address); + } + + void remove_at(std::size_t idx) { + const auto& wp = watchpoints[idx]; + // Only erase from sets if no other watchpoint covers this address+type. + bool other_read = false, other_write = false; + for (std::size_t i = 0; i < watchpoints.size(); i++) { + if (i == idx) continue; + if (watchpoints[i].address == wp.address) { + if (watchpoints[i].type & READ) other_read = true; + if (watchpoints[i].type & WRITE) other_write = true; + } + } + if (!other_read && (wp.type & READ)) watchpoint_reads.erase(wp.address); + if (!other_write && (wp.type & WRITE)) watchpoint_writes.erase(wp.address); + watchpoints.erase(watchpoints.begin() + idx); + } + + bool read_mem(word addr) { + if (!watchpoints.empty()) { + if (watchpoint_reads.count(addr)) { last_read_scanned = 1; return true; } + } + last_read_scanned = 0; + return false; + } + + bool write_mem(word addr) { + if (!watchpoints.empty()) { + if (watchpoint_writes.count(addr)) { last_write_scanned = 1; return true; } + } + last_write_scanned = 0; + return false; + } +}; + +static void test_defect_scans_all_on_miss() { + const int W = 500; + DefectMemory d; + for (int i = 0; i < W; ++i) + d.add(Watchpoint(word(0x4000 + i), READWRITE)); + + bool hit = d.read_mem(0x0000); + assert(!hit); + assert(d.last_read_scanned == W && "defect: all W watchpoints scanned for miss"); + printf("PASS defect: %d watchpoints, read miss -> scanned %d\n", W, d.last_read_scanned); + + hit = d.write_mem(0x0000); + assert(!hit); + assert(d.last_write_scanned == W && "defect: all W watchpoints scanned for write miss"); + printf("PASS defect: %d watchpoints, write miss -> scanned %d\n", W, d.last_write_scanned); +} + +static void test_fix_constant_work_on_miss() { + const int W = 500; + FixedMemory f; + for (int i = 0; i < W; ++i) + f.add(Watchpoint(word(0x4000 + i), READWRITE)); + + bool hit = f.read_mem(0x0000); + assert(!hit); + assert(f.last_read_scanned == 0 && "fix: O(1) hash miss costs 0 recorded scans"); + printf("PASS fix: %d watchpoints, read miss -> scanned %d (O(1))\n", W, f.last_read_scanned); + + hit = f.write_mem(0x0000); + assert(!hit); + assert(f.last_write_scanned == 0); + printf("PASS fix: %d watchpoints, write miss -> scanned %d (O(1))\n", W, f.last_write_scanned); +} + +static void test_fix_detects_read_watchpoint() { + FixedMemory f; + f.add(Watchpoint(0x2000, READ)); + f.add(Watchpoint(0x3000, WRITE)); + + assert(f.read_mem(0x2000) && "fix: READ watchpoint fires on read"); + assert(!f.read_mem(0x3000) && "fix: WRITE-only watchpoint does not fire on read"); + assert(f.write_mem(0x3000) && "fix: WRITE watchpoint fires on write"); + assert(!f.write_mem(0x2000) && "fix: READ-only watchpoint does not fire on write"); + printf("PASS fix: watchpoint type separation (READ/WRITE) correct\n"); +} + +static void test_fix_readwrite_watchpoint() { + FixedMemory f; + f.add(Watchpoint(0x1000, READWRITE)); + + assert(f.read_mem(0x1000) && "fix: READWRITE watchpoint fires on read"); + assert(f.write_mem(0x1000) && "fix: READWRITE watchpoint fires on write"); + printf("PASS fix: READWRITE watchpoint fires on both read and write\n"); +} + +static void test_fix_remove_at_cleans_sets() { + FixedMemory f; + f.add(Watchpoint(0x5000, READ)); + f.add(Watchpoint(0x6000, WRITE)); + + f.remove_at(0); // remove 0x5000/READ + + assert(!f.read_mem(0x5000) && "fix: removed watchpoint no longer fires on read"); + assert(f.write_mem(0x6000) && "fix: remaining watchpoint still fires"); + printf("PASS fix: remove_at cleans sets correctly\n"); +} + +static void test_fix_remove_shared_address_preserved() { + // Two watchpoints at same address, different types. Remove one, other survives. + FixedMemory f; + f.add(Watchpoint(0x7000, READ)); + f.add(Watchpoint(0x7000, WRITE)); + + f.remove_at(0); // remove READ watchpoint; WRITE should remain + + assert(!f.read_mem(0x7000) && "fix: READ watchpoint removed, no read hit"); + assert(f.write_mem(0x7000) && "fix: WRITE watchpoint survives after sibling removed"); + printf("PASS fix: shared-address partial remove keeps other type intact\n"); +} + +static void test_speedup_ratio() { + const int W = 500; + DefectMemory d; + FixedMemory f; + for (int i = 0; i < W; ++i) { + word addr = word(0xC000 + i); + d.add(Watchpoint(addr, READWRITE)); + f.add(Watchpoint(addr, READWRITE)); + } + + d.read_mem(0x0000); + f.read_mem(0x0000); + + printf("PASS speedup: defect=%d scans/read, fix=%d scans/read, ratio ~%dx\n", + d.last_read_scanned, f.last_read_scanned, d.last_read_scanned); + assert(d.last_read_scanned == W); + assert(f.last_read_scanned == 0); +} + +int main() { + printf("--- caprice32-0002 CWE-407 watchpoint O(W) scan per memory access ---\n"); + test_defect_scans_all_on_miss(); + test_fix_constant_work_on_miss(); + test_fix_detects_read_watchpoint(); + test_fix_readwrite_watchpoint(); + test_fix_remove_at_cleans_sets(); + test_fix_remove_shared_address_preserved(); + test_speedup_ratio(); + printf("ALL PASS\n"); + return 0; +}