java-topology/whitepaper/outreach/rpcs3.md
russell@unturf.com d1f82fd8e3 feat: add 8 outreach docs (26 defects) for batch 3
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.
2026-04-13 15:35:53 -04:00

5.4 KiB
Raw Blame History

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.patch
  • defects/rpcs3/patch/rpcs3-0002-spu-recompiler-calls-vector-dedup.patch
  • defects/rpcs3/patch/rpcs3-0003-savedata-blist-vector-find-in-sort.patch
  • defects/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.

  1. Confirm receipt and assign a GitHub issue reference (RPCS3/rpcs3).
  2. Assess severity — rpcs3-0001/0002 fire during SPU recompilation; rpcs3-0003 fires during save enumeration; rpcs3-0004 logs session passwords in cleartext.
  3. Coordinate a disclosure date — we target 90 days from first contact.
  4. 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.