From 659143809669d2e137b12b1971cecb4ddc978fa4 Mon Sep 17 00:00:00 2001 From: "russell@unturf.com" Date: Tue, 31 Mar 2026 17:36:57 -0400 Subject: [PATCH] duckstation: 2 CWE-407 defects, MOAD 0002-0005 CLEAN --- ...tion-0001-cheats-unique-prefixes-set.patch | 25 +++++ .../test/CheatsUniquePrefixesTest.class | Bin 0 -> 3470 bytes .../test/CheatsUniquePrefixesTest.java | 92 ++++++++++++++++++ ...ation-0002-cheats-enable-patches-set.patch | 22 +++++ .../test/CheatsEnablePatchesTest.class | Bin 0 -> 3278 bytes .../test/CheatsEnablePatchesTest.java | 91 +++++++++++++++++ 6 files changed, 230 insertions(+) create mode 100644 defects/duckstation-0001/patch/duckstation-0001-cheats-unique-prefixes-set.patch create mode 100644 defects/duckstation-0001/test/CheatsUniquePrefixesTest.class create mode 100644 defects/duckstation-0001/test/CheatsUniquePrefixesTest.java create mode 100644 defects/duckstation-0002/patch/duckstation-0002-cheats-enable-patches-set.patch create mode 100644 defects/duckstation-0002/test/CheatsEnablePatchesTest.class create mode 100644 defects/duckstation-0002/test/CheatsEnablePatchesTest.java diff --git a/defects/duckstation-0001/patch/duckstation-0001-cheats-unique-prefixes-set.patch b/defects/duckstation-0001/patch/duckstation-0001-cheats-unique-prefixes-set.patch new file mode 100644 index 000000000..fda509200 --- /dev/null +++ b/defects/duckstation-0001/patch/duckstation-0001-cheats-unique-prefixes-set.patch @@ -0,0 +1,25 @@ +# UNDF: UNDF-2026-XXXXXXXXX +--- a/src/core/cheats.cpp ++++ b/src/core/cheats.cpp +@@ -598,6 +598,8 @@ std::vector Cheats::GetCodeListUniquePrefixes(const CodeInfoLi + std::vector Cheats::GetCodeListUniquePrefixes(const CodeInfoList& list, bool include_empty) + { + std::vector ret; ++ // Use a hash set to test prefix membership in O(1) instead of O(N) std::find, ++ // eliminating O(N^2) behaviour when building our unique-prefix list. ++ std::unordered_set seen; + for (const Cheats::CodeInfo& code : list) + { + const std::string_view prefix = code.GetNameParentPart(); +@@ -608,7 +610,9 @@ std::vector Cheats::GetCodeListUniquePrefixes(const CodeInfoLi + continue; + } + +- if (std::find(ret.begin(), ret.end(), prefix) == ret.end()) ++ if (seen.insert(prefix).second) ++ { + ret.push_back(prefix); ++ } + } + return ret; + } diff --git a/defects/duckstation-0001/test/CheatsUniquePrefixesTest.class b/defects/duckstation-0001/test/CheatsUniquePrefixesTest.class new file mode 100644 index 0000000000000000000000000000000000000000..12166f4c780cb198d8829e89ec5262faeac1cc94 GIT binary patch literal 3470 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 getUniquePrefixesDefective(List codes) { + List ret = new ArrayList<>(); + for (String code : codes) { + // Simulate GetNameParentPart: prefix is everything before '/' + int slash = code.lastIndexOf('/'); + String prefix = (slash >= 0) ? code.substring(0, slash) : ""; + if (prefix.isEmpty()) continue; + if (!ret.contains(prefix)) { // O(N) scan per code + ret.add(prefix); + } + } + return ret; + } + + // --- Fixed: O(N) with hash set --- + static List getUniquePrefixesFixed(List codes) { + List ret = new ArrayList<>(); + Set seen = new HashSet<>(); + for (String code : codes) { + int slash = code.lastIndexOf('/'); + String prefix = (slash >= 0) ? code.substring(0, slash) : ""; + if (prefix.isEmpty()) continue; + if (seen.add(prefix)) { + ret.add(prefix); + } + } + return ret; + } + + public static void main(String[] args) { + int NUM_GROUPS = 200; + int CODES_PER_GROUP = 5; + int N = NUM_GROUPS * CODES_PER_GROUP; + + // Build test data: codes named "Group_N/Code_M" + List codes = new ArrayList<>(N); + for (int g = 0; g < NUM_GROUPS; g++) { + for (int c = 0; c < CODES_PER_GROUP; c++) { + codes.add("Group_" + g + "/Code_" + c); + } + } + + // Correctness check + List defResult = getUniquePrefixesDefective(codes); + List fixResult = getUniquePrefixesFixed(codes); + Collections.sort(defResult); + Collections.sort(fixResult); + assert defResult.equals(fixResult) + : "Prefix list mismatch: defective=" + defResult.size() + " fixed=" + fixResult.size(); + assert fixResult.size() == NUM_GROUPS : "Expected " + NUM_GROUPS + " unique prefixes"; + + // Warm up + for (int i = 0; i < 100; i++) { + getUniquePrefixesDefective(codes); + getUniquePrefixesFixed(codes); + } + + // Benchmark + int ITER = 1000; + long t0 = System.nanoTime(); + for (int i = 0; i < ITER; i++) getUniquePrefixesDefective(codes); + long defectNs = System.nanoTime() - t0; + + t0 = System.nanoTime(); + for (int i = 0; i < ITER; i++) getUniquePrefixesFixed(codes); + long fixedNs = System.nanoTime() - t0; + + double ratio = (double) defectNs / fixedNs; + System.out.printf("GetCodeListUniquePrefixes N=%d codes (%d groups) defect=%.1fms fixed=%.1fms ratio=%.1fx%n", + N, NUM_GROUPS, defectNs / 1e6, fixedNs / 1e6, ratio); + + assert ratio > 2.0 : "Expected >2x speedup, got " + ratio; + System.out.println("PASS"); + } +} diff --git a/defects/duckstation-0002/patch/duckstation-0002-cheats-enable-patches-set.patch b/defects/duckstation-0002/patch/duckstation-0002-cheats-enable-patches-set.patch new file mode 100644 index 000000000..4c1af6944 --- /dev/null +++ b/defects/duckstation-0002/patch/duckstation-0002-cheats-enable-patches-set.patch @@ -0,0 +1,22 @@ +# UNDF: UNDF-2026-XXXXXXXXX +--- a/src/core/cheats.cpp ++++ b/src/core/cheats.cpp +@@ -893,6 +893,8 @@ u32 Cheats::EnablePatches(const CheatCodeList& patches, const EnableCodeList& en + u32 Cheats::EnablePatches(const CheatCodeList& patches, const EnableCodeList& enable_list, const char* section, + bool hc_mode_active) + { ++ // Convert enable_list to a hash set for O(1) lookup instead of O(E) std::find ++ // per patch, eliminating O(P*E) behaviour when activating our patch/cheat lists. ++ const std::unordered_set enable_set(enable_list.begin(), enable_list.end()); + u32 count = 0; + for (const std::unique_ptr& p : patches) + { +@@ -905,7 +907,7 @@ u32 Cheats::EnablePatches(const CheatCodeList& patches, const EnableCodeList& en + if (p->GetMetadata().disallow_for_achievements && hc_mode_active) + continue; + +- if (std::find(enable_list.begin(), enable_list.end(), p->GetName()) == enable_list.end()) ++ if (!enable_set.count(p->GetName())) + continue; + + INFO_LOG("Enabled code from {}: {}", section, p->GetName()); diff --git a/defects/duckstation-0002/test/CheatsEnablePatchesTest.class b/defects/duckstation-0002/test/CheatsEnablePatchesTest.class new file mode 100644 index 0000000000000000000000000000000000000000..d4f741dd55b33a4a2f14503181d9edfee5a68688 GIT binary patch literal 3278 zcma)8TW}j!8UBv6(n|8i7saxzDoHnqomla$Nm_+iwj0NnM2+pZm1E-+C>!nC-dNfd z+Lf`nHGoMWKnoOlF_b`f$O|u&nIv|G+Z)61z~uoLW_aNR-gw}pohIpjcGs5dx(pi4 z?z#NuKi~iT-}(3a?%~ES0Sw~D5%}O2AxH=y$WVV(SyFoPiZS1F@yb;#XEOx*bVIiX z82s(Y=^BI(77>w91Bs#5F)!PCzGqCg$T+0iT1m0Z5<^paa;!3)@y63Ns70NKdI=4P zGDJUU!XPdv)`Yf9{%Ft3d_|*#7@8Oa!x?Ve^j(ECEuv*x09m`F8}kt~<1q;dzL}_7 zBZZ>9!lT$P;Q$^d#5vQj72U8HV(l*AYNyG|A`UV%RA8Q0tc9#*M0M!D(_g zw4f=rHDV}N^4g?g=N2?;O5r2;;_nBToE@`d~QiQ9fcpm5lrG)5zk4;VrskC zRxDd9kem#~FsJkaaYfpFCQJ*aCCuQX)HlmSZOkQ1H+v>Yo;GDlQwr%2E;Bs6Efcal z(ch-Zvg)wk-`0J6u3*XX9OrPWM`ukpo!)Yr5y8jsaS@-8@B%)$sac1=jA3i@8XZb; zNy(SBi*pWf)r#>f(!s&*)oUa)jq$=DcqH`;292-eo+Yc6c+?VxC52vGX$bum>kSzIld(c zpT;t=;lRinJ2c|0Nlr)b8GKg6=Olcdmyf!NAcyiKTq04`EWMzVSm7>=VXZ3l5)|6=O!p_6hSH*+}YGCJq+tntSAqWMk zYCO-?d~Ss%;yNW~mno8PUoP*J{7BX;mvY*u&Y|!7kaE$@X(5H2HBH;HOGu~S!GU?IFr)nmvk8@61~tpcw$y`HwIvh*`g*kt%Eh65wZMJhR(Dl7TC%;ch5 zoMg9}s3ClZp<_~^!lC5l-2dbBy9|jfrvoRJWvi%ZYPr}c&zrW)>VkMJg4gkeh&Q>A z))-nV+18CEb5ZMY$#d)PsFI_v+zP`R)e80uT>{Q4hML!`)-lsuEEm1JRhQ?g>~32) zwNlh}F|SUR`*BWJpFhvV5HXgeuW*_{${2=La-^zRBHm(X`#?;)K^1Y6Qt-b`=?uGZ z;ULv>A00wg-@=PJwE|S1={7;nFHmnky%`@pb{Fht)L?j#o_rdprvgmhB67e@zfA7{ zz0>~wW9twZCc4(Khv7{eqsCr_U!%EKhzoZRUqeGjTv*3GAJ!1O{pfG2K6d-jpZEQg zmiwFy4nU-KBlTj0p$SjWMc^djNZ~QsFoA0{OGW(w!c0W%dq|3CeuuXIqT}I91dP65 z9p1SiOxL#Fsz5mZ-r02zPo@O^>P-dS zM*kWTF(DSXiN^aFoaN083;}mB`YUI)ITlFVL<65pY#`&#bomp3{5qcTqc_+Y4|12H z^X^TBJfC7LwCR(Y8<=oc`cvUpxB;F3g5%UQxShkib=8X zE!4SIs}jxsho+h$+9}C1sg2ccne@)A)>fI+Hc2;{YHygQ>)*zu=&bk)_-6fydw4!O zE6`LU6!RW(+1UUYZFPbz^?dLS7G~TaTIw5Ny#5xVJo=XU4HP$txfHeTpv;x;3iCSU z8t9~NRyX!gt!ct>JdP8Ti<3xGdl08^o=(L@l3#&FUcvxgB}IIn4%bg;_BW)8-(wVi z!a4kv+W)`>{FCl5|H3#PVuA%R$!hQ{JBa64lCCKyaET3InyK_&po_?L%(9np+0oTm zNRQ}D1Vns=log^soyvD{^Z_~;i$BC^KEmFEkG%`(N=%?e^a1L;w<`Ea!7ptc_9vZF z4Kzfbx`UN9h{A0M{-2}268`4F=dupKC!L|25x(lM^EKR{w@ky|BR^kvp5JtyuQ< patches, List enableList) { + int count = 0; + for (String name : patches) { + if (!name.isEmpty() && !enableList.contains(name)) { // O(E) scan per patch + continue; + } + count++; + } + return count; + } + + // --- Fixed: O(P+E) with hash set --- + static int enablePatchesFixed(List patches, List enableList) { + Set enableSet = new HashSet<>(enableList); + int count = 0; + for (String name : patches) { + if (!name.isEmpty() && !enableSet.contains(name)) { // O(1) per patch + continue; + } + count++; + } + return count; + } + + public static void main(String[] args) { + int N = 500; + + List patches = new ArrayList<>(N); + List enableList = new ArrayList<>(N); + + for (int i = 0; i < N; i++) { + patches.add("Patch_" + i); + enableList.add("Patch_" + i); + } + + // Correctness check + int defCount = enablePatchesDefective(patches, enableList); + int fixCount = enablePatchesFixed(patches, enableList); + assert defCount == fixCount + : "Count mismatch: defective=" + defCount + " fixed=" + fixCount; + assert fixCount == N : "Expected all " + N + " patches enabled"; + + // Partial enable list + List partialEnable = enableList.subList(0, N / 2); + int defPartial = enablePatchesDefective(patches, partialEnable); + int fixPartial = enablePatchesFixed(patches, partialEnable); + assert defPartial == fixPartial + : "Partial count mismatch: defective=" + defPartial + " fixed=" + fixPartial; + + // Warm up + for (int i = 0; i < 200; i++) { + enablePatchesDefective(patches, enableList); + enablePatchesFixed(patches, enableList); + } + + // Benchmark + int ITER = 2000; + long t0 = System.nanoTime(); + for (int i = 0; i < ITER; i++) enablePatchesDefective(patches, enableList); + long defectNs = System.nanoTime() - t0; + + t0 = System.nanoTime(); + for (int i = 0; i < ITER; i++) enablePatchesFixed(patches, enableList); + long fixedNs = System.nanoTime() - t0; + + double ratio = (double) defectNs / fixedNs; + System.out.printf("EnablePatches N=%d defect=%.1fms fixed=%.1fms ratio=%.1fx%n", + N, defectNs / 1e6, fixedNs / 1e6, ratio); + + assert ratio > 2.0 : "Expected >2x speedup, got " + ratio; + System.out.println("PASS"); + } +}