fix(libScePngEnc): validate memory, image rows, and PNG output buffers in encoder

This commit is contained in:
fogydes authored and Adrià committed 2026-11-06 23:35:50 +01:00
1 parent ec1f584fad
commit bcffe2e2df
3 files changed
+31 -15

No files matched your search

+20 -10
View File
@@ -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<std::uintptr_t>(memoryAddress) + HANDLE_ALIGNMENT - 1) & ~(HANDLE_ALIGNMENT - 1);
auto* ctx = new (reinterpret_cast<void*>(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<PngEncContext*>(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<void*>(aligned), &localCtx, sizeof(localCtx))) {
return PNG_ENC_ERROR_INVALID_ADDR;
}
auto* ctx = reinterpret_cast<PngEncContext*>(aligned);
if (!GuestCopyOut_nid_no_patch(handle, &ctx, sizeof(ctx))) {
ctx->self = nullptr;
PngEncContext cleared{};
GuestCopyOut_nid_no_patch(reinterpret_cast<void*>(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<std::size_t>(width) * height * channels);
std::uint8_t* destination = pixels.data();
const std::size_t rowBytes = static_cast<std::size_t>(width) * 4;
std::vector<std::uint8_t> rowBuffer(rowBytes);
for (std::uint32_t y = 0; y < height; ++y) {
const std::uint8_t* source = localParam.image_mem_addr + static_cast<std::size_t>(y) * localParam.image_pitch;
const std::uint8_t* sourceRow = localParam.image_mem_addr + static_cast<std::size_t>(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<int>(png.size());
}
+5
View File
@@ -47,6 +47,7 @@ int main() {
Require(scePngEncCreate(&create, memory.data(), memorySize, nullptr) == invalidAddr);
Require(scePngEncCreate(reinterpret_cast<const PngEncCreateParam*>(0x10), memory.data(), memorySize, &handle) == invalidAddr);
Require(scePngEncCreate(&create, memory.data(), memorySize, reinterpret_cast<void**>(0x10)) == invalidAddr);
Require(scePngEncCreate(&create, reinterpret_cast<void*>(0x10), memorySize, &handle) == invalidAddr);
Require(scePngEncCreate(&create, memory.data(), memorySize - 1, &handle) == invalidSize);
Require(scePngEncCreate(&create, memory.data(), memorySize, &handle) == 0 && reinterpret_cast<std::uintptr_t>(handle) % 16 == 0);
@@ -146,6 +147,8 @@ int main() {
Require(scePngEncEncode(handle, &encode, reinterpret_cast<PngEncOutputInfo*>(0x10)) == invalidAddr);
invalid.image_mem_addr = nullptr;
Require(scePngEncEncode(handle, &invalid, nullptr) == invalidAddr);
invalid.image_mem_addr = reinterpret_cast<const std::uint8_t*>(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<std::uint8_t*>(0x10);
Require(scePngEncEncode(handle, &invalid, nullptr) == invalidAddr);
invalid = encode;
invalid.image_mem_size = 0;
Require(scePngEncEncode(handle, &invalid, nullptr) == invalidSize);
+6 -5
View File
@@ -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.