rpcs3 (4, C++), ppsspp (4, C++), spring-framework (3, Java), nats-server (3, Go), minio (3, Go), gimp (3, C), cockroach (3, Go), superset (3, Python). Note: rpcs3-0004 is CWE-312, rest are CWE-407.
5.4 KiB
RPCS3 — CWE-407 / CWE-312 Disclosure Brief
2026-04-13 · Patches available — awaiting upstream merge
Finding
Three O(n²) defects (CWE-407) and one cleartext password logging defect (CWE-312) in RPCS3 across the SPU recompiler, save data manager, and NP matchmaking system. All patched. Patches ready for upstream review. Two defects fire during SPU program recompilation; one fires during save data sorting; one logs session passwords in cleartext.
The Defects
rpcs3-0001 (PATCHED — MEDIUM): rpcs3/Emu/Cell/SPUCommonRecompiler.cpp:2899
// In add_block() — fires during SPU program analysis:
if (std::find(m_preds[target].begin(), m_preds[target].end(), pos) == m_preds[target].end())
{
m_preds[target].push_back(pos);
}
m_preds[target] holds predecessor block addresses as std::vector<u32>. std::find scans the entire vector for every predecessor insertion — O(P) per edge where P = predecessors of the target block. For SPU programs with many converging blocks, total cost reaches O(E×P).
rpcs3-0002 (PATCHED — MEDIUM): rpcs3/Emu/Cell/SPUCommonRecompiler.cpp:4842
// In SPU function analysis — fires for every external call target:
if (std::find(func.calls.begin(), func.calls.end(), target) == func.calls.end())
{
func.calls.push_back(target);
}
func.calls accumulates external call targets with std::find dedup — O(C) per target where C = accumulated calls. Nested inside a loop over all basic blocks and their targets: O(B×T×C) total.
rpcs3-0003 (PATCHED — MEDIUM): rpcs3/Emu/Cell/Modules/cellSaveData.cpp:1553
// In save data sorting — fires for every comparison:
std::sort(files_sorted.begin(), files_sorted.end(), [&](const fs::dir_entry& a, const fs::dir_entry& b) -> bool
{
const auto a_it = std::find(blist.begin(), blist.end(), a.name); // O(B)
const auto b_it = std::find(blist.begin(), blist.end(), b.name); // O(B)
Sort comparator calls std::find on the blist vector for both operands of every comparison — O(B) each. std::sort makes O(N log N) comparisons, so total cost reaches O(N log N × B). Large save file lists with many blist entries compound visibly.
rpcs3-0004 (PATCHED — MEDIUM): rpcs3/Emu/NP/np_structs_extra.cpp:124
// In print_SceNpMatching2CreateJoinRoomRequest — logs password bytes:
if (req->roomPassword)
sceNp2.warning("data: %s", fmt::buf_to_hexstring(req->roomPassword->data, sizeof(req->roomPassword->data)));
CWE-312: When a PS3 game creates or joins a password-protected online room via SceNpMatching2, RPCS3 logs the raw 8-byte session password in hexadecimal at WARNING severity. Any log persistence (file, remote aggregator, crash dump) captures the password in cleartext.
Complexity Proof
rpcs3-0001: At P=200 predecessors per target, E=500 edges:
- Defective: up to 200 + 199 + ... = ~20,000 comparisons per target block
- Fixed: 500 comparisons (unordered_set shadow index)
rpcs3-0002: At B=100 blocks, C=50 call targets:
- Defective: 100 × (50² / 2) = ~125,000 comparisons
- Fixed: 100 × 50 = 5,000 comparisons (unordered_set shadow)
- 25× op reduction.
rpcs3-0003: At N=200 files, B=100 blist entries:
- Defective: ~200 × 8 × 100 × 2 = ~320,000 comparisons
- Fixed: ~200 × 8 × 2 = ~3,200 comparisons (unordered_map pre-built)
- 100× op reduction.
rpcs3-0004: No complexity component — password redaction eliminates the exposure entirely.
Impact
RPCS3 emulates PlayStation 3 games on PC — used by hundreds of thousands of users for game preservation and compatibility testing. SPU recompilation (rpcs3-0001/0002) runs for every unique SPU program encountered during gameplay; complex games with large SPU kernels trigger these paths repeatedly. Save data sorting (rpcs3-0003) fires whenever a game enumerates save files — MMO and RPG titles with many save entries produce noticeable delays. The password logging defect (rpcs3-0004) exposes session credentials to any log consumer.
The Fix
rpcs3-0001: Maintain a parallel std::unordered_set<u32> per target block for O(1) dedup, keeping the vector for ordered iteration.
rpcs3-0002: Maintain a parallel unordered_set alongside func.calls for O(1) dedup.
rpcs3-0003: Pre-build an std::unordered_map<std::string, usz> from the blist for O(1) position lookup in the sort comparator.
rpcs3-0004: Replace buf_to_hexstring(req->roomPassword->data, ...) with a redacted placeholder: [REDACTED %zu bytes].
Patch
Fixes available:
defects/rpcs3/patch/rpcs3-0001-spu-recompiler-preds-vector-dedup.patchdefects/rpcs3/patch/rpcs3-0002-spu-recompiler-calls-vector-dedup.patchdefects/rpcs3/patch/rpcs3-0003-savedata-blist-vector-find-in-sort.patchdefects/rpcs3/patch/rpcs3-0004-np-room-password-cwe312.patch
Four patches across SPUCommonRecompiler.cpp, cellSaveData.cpp, and np_structs_extra.cpp.
What We Ask
Patches ready for review.
- Confirm receipt and assign a GitHub issue reference (RPCS3/rpcs3).
- Assess severity — rpcs3-0001/0002 fire during SPU recompilation; rpcs3-0003 fires during save enumeration; rpcs3-0004 logs session passwords in cleartext.
- Coordinate a disclosure date — we target 90 days from first contact.
- We will credit the RPCS3 team in the public disclosure. Preferred acknowledgment format welcome.
Contact: see cover email. This brief is confidential until coordinated disclosure.