decaf: 1 CWE-407 defect, MOAD 0002-0005 CLEAN
This commit is contained in:
parent
66f2e14770
commit
4fe601f541
2 changed files with 159 additions and 0 deletions
57
defects/decaf-0001/patch/decaf-0001.patch
Normal file
57
defects/decaf-0001/patch/decaf-0001.patch
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
# UNDF: UNDF-2026-XXXXXXXXX
|
||||
--- a/src/libdecaf/src/ios/mcp/ios_mcp_mcp_device.cpp
|
||||
+++ b/src/libdecaf/src/ios/mcp/ios_mcp_mcp_device.cpp
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "ios_mcp_config.h"
|
||||
#include "ios_mcp_enum.h"
|
||||
#include "ios_mcp_mcp_device.h"
|
||||
#include "ios_mcp_mcp_types.h"
|
||||
#include "ios_mcp_mcp_request.h"
|
||||
#include "ios_mcp_mcp_response.h"
|
||||
#include "ios_mcp_mcp_thread.h"
|
||||
#include "ios_mcp_title.h"
|
||||
|
||||
#include "cafe/libraries/cafe_hle.h"
|
||||
#include "decaf_config.h"
|
||||
#include "ios/ios.h"
|
||||
#include "ios/ios_stackobject.h"
|
||||
#include "ios/fs/ios_fs_fsa_ipc.h"
|
||||
#include "ios/kernel/ios_kernel_process.h"
|
||||
#include "ios/kernel/ios_kernel_resourcemanager.h"
|
||||
#include "vfs/vfs_host_device.h"
|
||||
#include "vfs/vfs_virtual_device.h"
|
||||
|
||||
#include <common/log.h>
|
||||
+#include <unordered_set>
|
||||
|
||||
namespace ios::mcp::internal
|
||||
{
|
||||
@@ -89,9 +90,9 @@ MCPError
|
||||
mcpGetFileLength(phys_ptr<const MCPRequestGetFileLength> request)
|
||||
{
|
||||
auto path = std::string { };
|
||||
auto name = std::string_view { phys_addrof(request->name).get() };
|
||||
|
||||
if (request->fileType == MCPFileType::CafeOS) {
|
||||
- if (std::find(decaf::config()->system.lle_modules.begin(),
|
||||
- decaf::config()->system.lle_modules.end(),
|
||||
- name) == decaf::config()->system.lle_modules.end()) {
|
||||
+ const auto &lleVec = decaf::config()->system.lle_modules;
|
||||
+ static const auto sLleModuleSet = std::unordered_set<std::string>(lleVec.begin(), lleVec.end());
|
||||
+ if (sLleModuleSet.find(std::string(name)) == sLleModuleSet.end()) {
|
||||
auto library = cafe::hle::getLibrary(name);
|
||||
if (library) {
|
||||
auto &rpl = library->getGeneratedRpl();
|
||||
@@ -155,9 +158,9 @@ mcpLoadFile(phys_ptr<const MCPRequestLoadFile> request,
|
||||
auto name = std::string_view { phys_addrof(request->name).get() };
|
||||
|
||||
if (request->fileType == MCPFileType::CafeOS) {
|
||||
- if (std::find(decaf::config()->system.lle_modules.begin(),
|
||||
- decaf::config()->system.lle_modules.end(),
|
||||
- name) == decaf::config()->system.lle_modules.end()) {
|
||||
+ const auto &lleVec = decaf::config()->system.lle_modules;
|
||||
+ static const auto sLleModuleSet = std::unordered_set<std::string>(lleVec.begin(), lleVec.end());
|
||||
+ if (sLleModuleSet.find(std::string(name)) == sLleModuleSet.end()) {
|
||||
auto library = cafe::hle::getLibrary(name);
|
||||
if (library) {
|
||||
auto &rpl = library->getGeneratedRpl();
|
||||
102
defects/decaf-0001/test/test_decaf_0001.cpp
Normal file
102
defects/decaf-0001/test/test_decaf_0001.cpp
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
// UNDF: UNDF-2026-XXXXXXXXX
|
||||
// Unit test for decaf-0001: lle_modules O(M) linear scan in mcpGetFileLength / mcpLoadFile
|
||||
//
|
||||
// Defect: mcpGetFileLength() and mcpLoadFile() in ios_mcp_mcp_device.cpp each call
|
||||
// std::find(lle_modules.begin(), lle_modules.end(), name)
|
||||
// This is O(M) per RPL load where M = lle_modules.size().
|
||||
// With R RPL files loaded at title startup the total cost is O(R * M).
|
||||
// A Wii U game loads 40-60 RPL modules; with all 40 native modules in lle_modules
|
||||
// that is 60 * 2 * 40 = 4800 string comparisons at title load time.
|
||||
//
|
||||
// Fix: replace std::find on the vector with a lookup on a std::unordered_set built once
|
||||
// from lle_modules at first call. O(1) amortised per lookup.
|
||||
//
|
||||
// Build:
|
||||
// g++ -std=c++17 -O2 -o test_decaf_0001 test_decaf_0001.cpp && ./test_decaf_0001
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <chrono>
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
static bool defective_lookup(const std::vector<std::string> &v, const std::string &name)
|
||||
{
|
||||
// Original pattern: O(M) linear scan on every call.
|
||||
return std::find(v.begin(), v.end(), name) != v.end();
|
||||
}
|
||||
|
||||
static bool patched_lookup(const std::unordered_set<std::string> &s, const std::string &name)
|
||||
{
|
||||
// Patched pattern: O(1) hash lookup, set built once from the vector at startup.
|
||||
return s.count(name) > 0;
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
// Realistic scenario: M lle_modules entries, R RPL queries per title load.
|
||||
// Half the queries are present-at-end (worst case scan), half are absent (full scan).
|
||||
const int M = 40; // lle_modules count (all native modules enabled as LLE)
|
||||
const int R = 60; // RPL files a typical Wii U game loads
|
||||
|
||||
std::vector<std::string> lleVec;
|
||||
for (int i = 0; i < M; ++i)
|
||||
lleVec.push_back("lle_module_" + std::to_string(i) + ".rpl");
|
||||
|
||||
auto lleSet = std::unordered_set<std::string>(lleVec.begin(), lleVec.end());
|
||||
|
||||
// Build queries: half at end of list (worst-case position), half absent.
|
||||
std::vector<std::string> queries;
|
||||
for (int i = 0; i < R / 2; ++i)
|
||||
queries.push_back(lleVec[M - 1 - (i % M)]); // last entries -- near-full scan
|
||||
for (int i = 0; i < R - R / 2; ++i)
|
||||
queries.push_back("hle_lib_" + std::to_string(i) + ".rpl"); // absent -- full scan
|
||||
|
||||
// Correctness -----------------------------------------------------------
|
||||
for (const auto &q : queries)
|
||||
assert(defective_lookup(lleVec, q) == patched_lookup(lleSet, q));
|
||||
assert(defective_lookup(lleVec, "lle_module_0.rpl") == true);
|
||||
assert(patched_lookup(lleSet, "lle_module_0.rpl") == true);
|
||||
assert(defective_lookup(lleVec, "absent.rpl") == false);
|
||||
assert(patched_lookup(lleSet, "absent.rpl") == false);
|
||||
std::printf("Correctness: PASS\n");
|
||||
|
||||
// Op-count model --------------------------------------------------------
|
||||
// Defective: each query scans up to M entries (worst case full scan for absent/end entries).
|
||||
// Patched: each query costs O(1).
|
||||
// With 2 IPC calls per RPL (mcpGetFileLength + mcpLoadFile):
|
||||
long long opDef = (long long)M * R * 2;
|
||||
long long opPatch = R * 2;
|
||||
double opRatio = (double)opDef / opPatch;
|
||||
std::printf("Op-count defective: %lld patched: %lld ratio: %.1fx\n", opDef, opPatch, opRatio);
|
||||
assert(opRatio >= (double)M);
|
||||
std::printf("Op-count ratio >= M=%d: PASS\n", M);
|
||||
|
||||
// Timing ----------------------------------------------------------------
|
||||
const int iters = 200000;
|
||||
volatile int sink = 0;
|
||||
|
||||
auto t0 = std::chrono::high_resolution_clock::now();
|
||||
for (int i = 0; i < iters; ++i)
|
||||
for (const auto &q : queries)
|
||||
sink += defective_lookup(lleVec, q) ? 1 : 0;
|
||||
auto t1 = std::chrono::high_resolution_clock::now();
|
||||
|
||||
auto t2 = std::chrono::high_resolution_clock::now();
|
||||
for (int i = 0; i < iters; ++i)
|
||||
for (const auto &q : queries)
|
||||
sink += patched_lookup(lleSet, q) ? 1 : 0;
|
||||
auto t3 = std::chrono::high_resolution_clock::now();
|
||||
|
||||
double msDef = std::chrono::duration<double, std::milli>(t1 - t0).count();
|
||||
double msPatch = std::chrono::duration<double, std::milli>(t3 - t2).count();
|
||||
double ratio = msDef / msPatch;
|
||||
std::printf("Defective: %.2f ms Patched: %.2f ms ratio: %.1fx\n", msDef, msPatch, ratio);
|
||||
assert(ratio >= 1.5);
|
||||
std::printf("Performance: %.1fx speedup, PASS (sink=%d)\n", ratio, (int)sink);
|
||||
|
||||
return 0;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue