From bcffe2e2df490d95d40c5bacc508d629cfeeb9b0 Mon Sep 17 00:00:00 2001 From: fogydes Date: Mon, 2 Nov 2026 17:51:13 +0530 Subject: [PATCH] fix(libScePngEnc): validate memory, image rows, and PNG output buffers in encoder --- core/libs/prx/libScePngEnc/Export.cpp | 30 ++++++++++++++++++--------- core/libs/tests/GuestPngEnc.cpp | 5 +++++ docs/dev/debt/libScePngEnc.md | 11 +++++----- 3 files changed, 31 insertions(+), 15 deletions(-) diff --git a/core/libs/prx/libScePngEnc/Export.cpp b/core/libs/prx/libScePngEnc/Export.cpp index b984afc135..663dfb5ef1 100644 --- a/core/libs/prx/libScePngEnc/Export.cpp +++ b/core/libs/prx/libScePngEnc/Export.cpp @@ -97,14 +97,19 @@ int APS5_VABI scePngEncCreate(const PngEncCreateParam* param, void* memoryAddres if (memorySize < requiredSize) return PNG_ENC_ERROR_INVALID_SIZE; if (!handle) return PNG_ENC_ERROR_INVALID_ADDR; const std::uintptr_t aligned = (reinterpret_cast(memoryAddress) + HANDLE_ALIGNMENT - 1) & ~(HANDLE_ALIGNMENT - 1); - auto* ctx = new (reinterpret_cast(aligned)) PngEncContext{}; - ctx->self = ctx; - ctx->size = sizeof(PngEncCreateParam); - ctx->maxImageWidth = (local.max_image_width + 7) & ~std::uint32_t{7}; - ctx->maxFilterNumber = local.max_filter_number; - ctx->scratchSize = requiredSize - 112; + PngEncContext localCtx{}; + localCtx.self = reinterpret_cast(aligned); + localCtx.size = sizeof(PngEncCreateParam); + localCtx.maxImageWidth = (local.max_image_width + 7) & ~std::uint32_t{7}; + localCtx.maxFilterNumber = local.max_filter_number; + localCtx.scratchSize = requiredSize - 112; + if (!GuestCopyOut_nid_no_patch(reinterpret_cast(aligned), &localCtx, sizeof(localCtx))) { + return PNG_ENC_ERROR_INVALID_ADDR; + } + auto* ctx = reinterpret_cast(aligned); if (!GuestCopyOut_nid_no_patch(handle, &ctx, sizeof(ctx))) { - ctx->self = nullptr; + PngEncContext cleared{}; + GuestCopyOut_nid_no_patch(reinterpret_cast(aligned), &cleared, sizeof(cleared)); return PNG_ENC_ERROR_INVALID_ADDR; } return 0; @@ -113,7 +118,8 @@ int APS5_VABI scePngEncCreate(const PngEncCreateParam* param, void* memoryAddres int APS5_VABI scePngEncDelete(void* handle) { PngEncContext* ctx = context(handle); if (!ctx) return PNG_ENC_ERROR_INVALID_HANDLE; - ctx->self = nullptr; + PngEncContext cleared{}; + GuestCopyOut_nid_no_patch(ctx, &cleared, sizeof(cleared)); return 0; } @@ -145,8 +151,12 @@ int APS5_VABI scePngEncEncode(void* handle, const PngEncEncodeParam* param, PngE try { pixels.resize(static_cast(width) * height * channels); std::uint8_t* destination = pixels.data(); + const std::size_t rowBytes = static_cast(width) * 4; + std::vector rowBuffer(rowBytes); for (std::uint32_t y = 0; y < height; ++y) { - const std::uint8_t* source = localParam.image_mem_addr + static_cast(y) * localParam.image_pitch; + const std::uint8_t* sourceRow = localParam.image_mem_addr + static_cast(y) * localParam.image_pitch; + if (!GuestCopyIn_nid_no_patch(rowBuffer.data(), sourceRow, rowBytes)) return PNG_ENC_ERROR_INVALID_ADDR; + const std::uint8_t* source = rowBuffer.data(); for (std::uint32_t x = 0; x < width; ++x, source += 4) { *destination++ = source[bgr ? 2 : 0]; *destination++ = source[1]; @@ -165,7 +175,7 @@ int APS5_VABI scePngEncEncode(void* handle, const PngEncEncodeParam* param, PngE if (!GuestCopyOut_nid_no_patch(outputInfo, &localOutput, sizeof(localOutput))) return PNG_ENC_ERROR_INVALID_ADDR; } if (overflow) return PNG_ENC_ERROR_DATA_OVERFLOW; - std::copy(png.begin(), png.end(), localParam.png_mem_addr); + if (!GuestCopyOut_nid_no_patch(localParam.png_mem_addr, png.data(), png.size())) return PNG_ENC_ERROR_INVALID_ADDR; return static_cast(png.size()); } diff --git a/core/libs/tests/GuestPngEnc.cpp b/core/libs/tests/GuestPngEnc.cpp index d8f89448c8..189b302287 100644 --- a/core/libs/tests/GuestPngEnc.cpp +++ b/core/libs/tests/GuestPngEnc.cpp @@ -47,6 +47,7 @@ int main() { Require(scePngEncCreate(&create, memory.data(), memorySize, nullptr) == invalidAddr); Require(scePngEncCreate(reinterpret_cast(0x10), memory.data(), memorySize, &handle) == invalidAddr); Require(scePngEncCreate(&create, memory.data(), memorySize, reinterpret_cast(0x10)) == invalidAddr); + Require(scePngEncCreate(&create, reinterpret_cast(0x10), memorySize, &handle) == invalidAddr); Require(scePngEncCreate(&create, memory.data(), memorySize - 1, &handle) == invalidSize); Require(scePngEncCreate(&create, memory.data(), memorySize, &handle) == 0 && reinterpret_cast(handle) % 16 == 0); @@ -146,6 +147,8 @@ int main() { Require(scePngEncEncode(handle, &encode, reinterpret_cast(0x10)) == invalidAddr); invalid.image_mem_addr = nullptr; Require(scePngEncEncode(handle, &invalid, nullptr) == invalidAddr); + invalid.image_mem_addr = reinterpret_cast(0x10); + Require(scePngEncEncode(handle, &invalid, nullptr) == invalidAddr); for (auto change : {+[](PngEncEncodeParam& p) { p.pixel_format = 2; }, +[](PngEncEncodeParam& p) { p.color_space = 4; }, +[](PngEncEncodeParam& p) { p.bit_depth = 16; }, +[](PngEncEncodeParam& p) { p.clut_number = 1; }, +[](PngEncEncodeParam& p) { p.filter_type = 16; }, +[](PngEncEncodeParam& p) { p.compression_level = 10; }}) { @@ -165,6 +168,8 @@ int main() { invalid = encode; invalid.png_mem_addr = nullptr; Require(scePngEncEncode(handle, &invalid, nullptr) == invalidAddr); + invalid.png_mem_addr = reinterpret_cast(0x10); + Require(scePngEncEncode(handle, &invalid, nullptr) == invalidAddr); invalid = encode; invalid.image_mem_size = 0; Require(scePngEncEncode(handle, &invalid, nullptr) == invalidSize); diff --git a/docs/dev/debt/libScePngEnc.md b/docs/dev/debt/libScePngEnc.md index 88f5089fbc..abde4afd7e 100644 --- a/docs/dev/debt/libScePngEnc.md +++ b/docs/dev/debt/libScePngEnc.md @@ -1,5 +1,6 @@ -# libScePngEnc technical debt - -### Functional - -- [scePngEncEncode](../../../core/libs/prx/libScePngEnc/Export.cpp): native filter setup always includes the unfiltered candidate, including for partial masks. The host now permits it too; exact filter-selection ties, compressed bytes and partial-output behavior are not matched to the native encoder. Creation validation, work-memory size, 16-byte handle alignment and the context prefix now match the supplied module. Input-buffer alignment, pitch, dimensions, buffer capacity and selected-filter count now follow the native checks. Host scratch storage remains separately allocated. +# libScePngEnc technical debt + +### Functional + +- [libScePngEnc](../../../core/libs/prx/libScePngEnc/Export.cpp) returns SCE_PNG_ENC_ERROR_INVALID_ADDR for unmapped or unwritable pointer arguments and buffers and SCE_PNG_ENC_ERROR_INVALID_HANDLE for an unreadable handle. This is a defensive host-protection fallback; console behaviour for unmapped or read-only memory is unmeasured. +- [scePngEncEncode](../../../core/libs/prx/libScePngEnc/Export.cpp): native filter setup always includes the unfiltered candidate, including for partial masks. The host now permits it too; exact filter-selection ties, compressed bytes and partial-output behavior are not matched to the native encoder. Creation validation, work-memory size, 16-byte handle alignment and the context prefix now match the supplied module. Input-buffer alignment, pitch, dimensions, buffer capacity and selected-filter count now follow the native checks. Host scratch storage remains separately allocated.