Filesystem: harden disc/title parsers against malformed input

- WUD: compute the index-table size in 64-bit and validate it against the file size before allocating, and null-check the allocation. A crafted uncompressedSize could previously overflow a 32-bit size and under-allocate, leading to out-of-bounds access.

- WUHB: guard against a zero-sized hash table (division by zero) and cap hash-chain traversal to avoid an infinite loop on cyclic chains.

- WUHB directory listing and FSA ReadDir: use bounded, always-NUL-terminated name copies (a 128-255 byte filename could overflow the guest response).
This commit is contained in:
1e1 2026-06-29 23:02:41 +02:00
parent d973fde40b
commit 125355b7e1
4 changed files with 27 additions and 5 deletions

View File

@ -43,8 +43,20 @@ wud_t* wud_open(const fs::path& path)
wud->offsetSectorArray = (wud->offsetSectorArray + (long long)(wud->sectorSize-1));
wud->offsetSectorArray = wud->offsetSectorArray - (wud->offsetSectorArray%(long long)wud->sectorSize);
// read index table
unsigned int indexTableSize = sizeof(unsigned int) * wud->indexTableEntryCount;
uint64 indexTableSize64 = (uint64)sizeof(unsigned int) * (uint64)wud->indexTableEntryCount;
if (indexTableSize64 == 0 || indexTableSize64 > 0x40000000ull || indexTableSize64 > (uint64)inputFileSize)
{
// reject implausible/crafted index table sizes to avoid integer overflow and over-allocation
wud_close(wud);
return nullptr;
}
unsigned int indexTableSize = (unsigned int)indexTableSize64;
wud->indexTable = (unsigned int*)malloc(indexTableSize);
if (!wud->indexTable)
{
wud_close(wud);
return nullptr;
}
wud->fs->SetPosition(wud->offsetIndexTable);
if( wud->fs->readData(wud->indexTable, indexTableSize) != indexTableSize )
{

View File

@ -115,6 +115,8 @@ uint32 WUHBReader::GetHashTableEntryOffset(uint32 hash, bool isFile) const
const uint64 hash_table_ofs = (isFile ? m_header.file_hash_table_ofs : m_header.dir_hash_table_ofs);
const uint64 hash_table_entry_count = hash_table_size / sizeof(uint32);
if (hash_table_entry_count == 0)
return ROMFS_ENTRY_EMPTY;
const uint64 hash_table_entry_offset = hash_table_ofs + (hash % hash_table_entry_count) * sizeof(uint32);
m_fileIn->SetPosition(hash_table_entry_offset);
@ -131,10 +133,15 @@ uint32 WUHBReader::GetHashTableEntryOffset(uint32 hash, bool isFile) const
template<bool T>
bool WUHBReader::SearchHashList(uint32& entryOffset, const fs::path& targetName) const
{
for (;;)
for (uint32 iterations = 0; ; iterations++)
{
if (entryOffset == ROMFS_ENTRY_EMPTY)
return false;
if (iterations >= 0x100000)
{
cemuLog_log(LogType::Force, "WUHB: aborting suspected cyclic hash chain");
return false;
}
auto entry = GetEntry<T>(entryOffset);
if (entry.name == targetName)

View File

@ -75,7 +75,8 @@ class FSCDeviceWuhbFileCtx : public FSCVirtualFile
dirEntry->isDirectory = true;
dirEntry->isFile = false;
dirEntry->fileSize = 0;
std::strncpy(dirEntry->path, entry.name.c_str(), FSC_MAX_DIR_NAME_LENGTH);
std::strncpy(dirEntry->path, entry.name.c_str(), FSC_MAX_DIR_NAME_LENGTH - 1);
dirEntry->path[FSC_MAX_DIR_NAME_LENGTH - 1] = '\0';
return true;
}
}
@ -88,7 +89,8 @@ class FSCDeviceWuhbFileCtx : public FSCVirtualFile
dirEntry->isDirectory = false;
dirEntry->isFile = true;
dirEntry->fileSize = entry.size;
std::strncpy(dirEntry->path, entry.name.c_str(), FSC_MAX_DIR_NAME_LENGTH);
std::strncpy(dirEntry->path, entry.name.c_str(), FSC_MAX_DIR_NAME_LENGTH - 1);
dirEntry->path[FSC_MAX_DIR_NAME_LENGTH - 1] = '\0';
return true;
}
}

View File

@ -556,7 +556,8 @@ namespace iosu
FSCDirEntry fscDirEntry;
if (fsc_nextDir(fscFile, &fscDirEntry) == false)
return FSA_RESULT::END_OF_DIRECTORY;
strcpy(dirEntryOut->name, fscDirEntry.path);
strncpy(dirEntryOut->name, fscDirEntry.path, sizeof(dirEntryOut->name));
dirEntryOut->name[sizeof(dirEntryOut->name) - 1] = '\0';
FSFlag statFlag = FSFlag::NONE;
dirEntryOut->stat.size = 0;
if (fscDirEntry.isDirectory)