From 9372d4b490a58cbf02cc49ecb325f58e406ed5aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Fri, 9 Oct 2026 14:31:36 +0200 Subject: [PATCH] sceIo: Validate PGD block sizes and dir listing state The PGD block size comes from the file header (or a savestate), and is used to size the block buffer and as a divisor and mask, so require a sane power of two. On load, force the next read to fill the buffer, which isn't saved. sceIoDread only checks the listing index against the end. Co-Authored-By: Claude Opus 5.5 (1M context) --- Core/HLE/sceIo.cpp | 21 ++++++++++++++++++++- ext/libkirk/amctrl.c | 9 +++++++++ ext/libkirk/amctrl.h | 5 +++++ 3 files changed, 34 insertions(+), 1 deletion(-) diff --git a/Core/HLE/sceIo.cpp b/Core/HLE/sceIo.cpp index 8d4b05aac2..07a16b44ab 100644 --- a/Core/HLE/sceIo.cpp +++ b/Core/HLE/sceIo.cpp @@ -275,7 +275,19 @@ public: if (pgdInfo) p.DoVoid(pgdInfo, sizeof(PGD_DESC)); if (p.mode == p.MODE_READ && pgdInfo) { - pgdInfo->block_buf = (u8 *)malloc(pgdInfo->block_size * 2); + pgdInfo->block_buf = nullptr; + if (pgd_valid_block_size(pgdInfo->block_size)) { + pgdInfo->block_buf = (u8 *)malloc(pgdInfo->block_size * 2); + } + if (!pgdInfo->block_buf) { + ERROR_LOG(Log::sceIo, "Savestate has an invalid PGD block size"); + p.SetError(p.ERROR_FAILURE); + pgd_close(pgdInfo); + pgdInfo = nullptr; + return; + } + // The buffer isn't saved, so make the next read fill it. + pgdInfo->current_block = -1; } } @@ -2510,6 +2522,13 @@ public: // TODO: Is this the right way for it to wake up? int count = (int) listing.size(); Do(p, count); + // sceIoDread only checks for index == count. + if (count < 0 || index < 0 || index > count || !p.CheckRead(count)) { + ERROR_LOG(Log::sceIo, "Savestate has an invalid dir listing"); + p.SetError(p.ERROR_FAILURE); + index = 0; + return; + } listing.resize(count); for (int i = 0; i < count; ++i) { listing[i].DoState(p); diff --git a/ext/libkirk/amctrl.c b/ext/libkirk/amctrl.c index 688f7fe1bd..fdd05b80d5 100644 --- a/ext/libkirk/amctrl.c +++ b/ext/libkirk/amctrl.c @@ -678,6 +678,11 @@ PGD_DESC *pgd_open(KirkState *kirk, u8 *pgd_buf, int pgd_flag, u8 *pgd_vkey) pgd->data_offset = *(u32*)(pgd_buf+0x4c); memcpy(pgd->dkey, pgd_buf+0x30, 16); + if(!pgd_valid_block_size(pgd->block_size)){ + free(pgd); + return NULL; + } + pgd->align_size = (pgd->data_size+15)&~15; pgd->table_offset = pgd->data_offset+pgd->align_size; pgd->block_nr = (pgd->align_size+pgd->block_size-1)&~(pgd->block_size-1); @@ -686,6 +691,10 @@ PGD_DESC *pgd_open(KirkState *kirk, u8 *pgd_buf, int pgd_flag, u8 *pgd_vkey) pgd->file_offset = 0; pgd->current_block = -1; pgd->block_buf = (u8*)malloc(pgd->block_size*2); + if(pgd->block_buf==NULL){ + free(pgd); + return NULL; + } return pgd; } diff --git a/ext/libkirk/amctrl.h b/ext/libkirk/amctrl.h index af6dbdcd12..c4ef749249 100644 --- a/ext/libkirk/amctrl.h +++ b/ext/libkirk/amctrl.h @@ -41,6 +41,11 @@ typedef struct { u32 file_offset; }PGD_DESC; +// block_size comes from the file (or a savestate). Reads mask with block_size - 1, so it must be a power of two. +static inline int pgd_valid_block_size(u32 block_size) { + return block_size >= 16 && block_size <= 0x100000 && (block_size & (block_size - 1)) == 0; +} + // type: // 2: use fuse id