Summary
A few memory-safety issues in the disc/title/homebrew parsers are reachable purely by loading a crafted or corrupt file. Severity is moderate (no RCE demonstrated, and the guest address space is a fixed mapping), but they are easy to trigger and cheap to harden.
1. WUHB — divide-by-zero on a zero-sized hash table
WUHBReader::GetHashTableEntryOffset (src/Cafe/Filesystem/WUHB/WUHBReader.cpp:117):
const uint64 hash_table_entry_count = hash_table_size / sizeof(uint32);
... (hash % hash_table_entry_count) ...
A header with *_hash_table_size < 4 makes hash_table_entry_count == 0 → % 0 (SIGFPE) on the first file lookup. hash_table_size is never validated.
2. WUHB — unbounded hash-chain traversal
WUHBReader::SearchHashList (:134) follows entryOffset = entry.hash in a for(;;) with no visited set or iteration cap; two entries referencing each other loop forever (each iteration doing file I/O).
3. WUHB — directory name copy not NUL-terminated
src/Cafe/Filesystem/fscDeviceWuhb.cpp:78 and :91:
std::strncpy(dirEntry->path, entry.name.c_str(), FSC_MAX_DIR_NAME_LENGTH);
strncpy with count == sizeof(dest) writes no terminator when the (file-controlled) name length ≥ FSC_MAX_DIR_NAME_LENGTH. HostFS and WUD both truncate-and-terminate; WUHB is the outlier.
4. FSA ReadDir — unbounded strcpy of the directory entry name
src/Cafe/IOSU/fsa/iosu_fsa.cpp:559:
strcpy(dirEntryOut->name, fscDirEntry.path);
copies from a 256-byte source (FSCDirEntry.path) into the 128-byte guest FSDirEntry_t.name; a 128–255 byte filename overflows the guest response. The getCwd handler just below already uses a bounded strncpy + explicit NUL and is the pattern to follow.
5. WUD/WUX — 32-bit multiply truncation → under-allocation → OOB
src/Cafe/Filesystem/WUD/wud.cpp:46:
unsigned int indexTableSize = sizeof(unsigned int) * wud->indexTableEntryCount;
indexTableEntryCount derives from the file-controlled uncompressedSize/sectorSize. The product is computed as size_t but truncated into a 32-bit unsigned int, so a large uncompressedSize wraps to a small value; the following malloc (also unchecked for null) under-allocates and later indexTable[sectorIndex] reads out of bounds. uncompressedSize is not range-validated.
Suggested hardening
Validate the sizes against the actual file length before allocating, compute in 64-bit + null-check the allocation, bound the hash-chain loop, and use bounded/NUL-terminated copies for the name fields. Happy to help if useful.
Summary
A few memory-safety issues in the disc/title/homebrew parsers are reachable purely by loading a crafted or corrupt file. Severity is moderate (no RCE demonstrated, and the guest address space is a fixed mapping), but they are easy to trigger and cheap to harden.
1. WUHB — divide-by-zero on a zero-sized hash table
WUHBReader::GetHashTableEntryOffset(src/Cafe/Filesystem/WUHB/WUHBReader.cpp:117):A header with
*_hash_table_size < 4makeshash_table_entry_count == 0→% 0(SIGFPE) on the first file lookup.hash_table_sizeis never validated.2. WUHB — unbounded hash-chain traversal
WUHBReader::SearchHashList(:134) followsentryOffset = entry.hashin afor(;;)with no visited set or iteration cap; two entries referencing each other loop forever (each iteration doing file I/O).3. WUHB — directory name copy not NUL-terminated
src/Cafe/Filesystem/fscDeviceWuhb.cpp:78and:91:std::strncpy(dirEntry->path, entry.name.c_str(), FSC_MAX_DIR_NAME_LENGTH);strncpywithcount == sizeof(dest)writes no terminator when the (file-controlled) name length ≥FSC_MAX_DIR_NAME_LENGTH. HostFS and WUD both truncate-and-terminate; WUHB is the outlier.4. FSA ReadDir — unbounded strcpy of the directory entry name
src/Cafe/IOSU/fsa/iosu_fsa.cpp:559:strcpy(dirEntryOut->name, fscDirEntry.path);copies from a 256-byte source (
FSCDirEntry.path) into the 128-byte guestFSDirEntry_t.name; a 128–255 byte filename overflows the guest response. ThegetCwdhandler just below already uses a boundedstrncpy+ explicit NUL and is the pattern to follow.5. WUD/WUX — 32-bit multiply truncation → under-allocation → OOB
src/Cafe/Filesystem/WUD/wud.cpp:46:indexTableEntryCountderives from the file-controlleduncompressedSize/sectorSize. The product is computed assize_tbut truncated into a 32-bitunsigned int, so a largeuncompressedSizewraps to a small value; the followingmalloc(also unchecked for null) under-allocates and laterindexTable[sectorIndex]reads out of bounds.uncompressedSizeis not range-validated.Suggested hardening
Validate the sizes against the actual file length before allocating, compute in 64-bit + null-check the allocation, bound the hash-chain loop, and use bounded/NUL-terminated copies for the name fields. Happy to help if useful.