mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-22 00:22:57 -05:00
fix(jpegdec): enforce native decode parameter limits
This commit is contained in:
1 parent
65f686c336
commit
a058d8d8fa
3 files changed
+43
-20
No files matched your search
@@ -22,7 +22,7 @@ constexpr std::int32_t SCE_JPEG_DEC_ERROR_INVALID_ADDR = static_cast<std::int32_
|
||||
constexpr std::int32_t SCE_JPEG_DEC_ERROR_INVALID_SIZE = static_cast<std::int32_t>(0x80650002);
|
||||
constexpr std::int32_t SCE_JPEG_DEC_ERROR_INVALID_PARAM = static_cast<std::int32_t>(0x80650003);
|
||||
constexpr std::int32_t SCE_JPEG_DEC_ERROR_INVALID_HANDLE = static_cast<std::int32_t>(0x80650004);
|
||||
constexpr std::int32_t SCE_JPEG_DEC_ERROR_INVALID_WORK_MEMORY = static_cast<std::int32_t>(0x80650005);
|
||||
constexpr std::int32_t SCE_JPEG_DEC_ERROR_IMAGE_TOO_WIDE = static_cast<std::int32_t>(0x80650006);
|
||||
constexpr std::int32_t SCE_JPEG_DEC_ERROR_INVALID_DATA = static_cast<std::int32_t>(0x80650010);
|
||||
constexpr std::int32_t SCE_JPEG_DEC_ERROR_DECODE_ERROR = static_cast<std::int32_t>(0x80650012);
|
||||
|
||||
@@ -66,11 +66,12 @@ std::uint32_t workMemorySize(const JpegDecCreateParam& param) {
|
||||
return MEMORY_OVERHEAD + (alignedWidth + 16) * (param.attribute == ATTRIBUTE_CSC_ORDINARY ? 54 : 166);
|
||||
}
|
||||
|
||||
std::uint32_t requireDownScale(const char* function, std::uint16_t decodeMode, std::uint16_t downScale) {
|
||||
bool validDecodeOptions(std::uint16_t decodeMode, std::uint16_t downScale) {
|
||||
return decodeMode <= 1 && (downScale == DOWN_SCALE_NONE || downScale == DOWN_SCALE_HALF || downScale == DOWN_SCALE_QUARTER || downScale == DOWN_SCALE_EIGHTH);
|
||||
}
|
||||
|
||||
void requireDecodeMode(const char* function, std::uint16_t decodeMode) {
|
||||
if (decodeMode != DECODE_MODE_NORMAL) throw std::runtime_error(std::string(function) + ": decode mode " + std::to_string(decodeMode) + " is not implemented");
|
||||
if (downScale != DOWN_SCALE_NONE && downScale != DOWN_SCALE_HALF && downScale != DOWN_SCALE_QUARTER && downScale != DOWN_SCALE_EIGHTH)
|
||||
throw std::runtime_error(std::string(function) + ": down scale " + std::to_string(downScale) + " is not implemented");
|
||||
return downScale;
|
||||
}
|
||||
|
||||
Context* toContext(void* handle) {
|
||||
@@ -118,13 +119,16 @@ int32_t APS5_VABI sceJpegDecCreate(const JpegDecCreateParam* param, void* memory
|
||||
int32_t APS5_VABI sceJpegDecDecode(void* handle, const JpegDecDecodeParam* param, JpegDecImageInfo* imageInfo) {
|
||||
const Context* context = toContext(handle);
|
||||
if (!context) return SCE_JPEG_DEC_ERROR_INVALID_HANDLE;
|
||||
(void)context;
|
||||
if (!param) return SCE_JPEG_DEC_ERROR_INVALID_ADDR;
|
||||
if (!param->jpeg_mem_addr || !param->image_mem_addr || (reinterpret_cast<std::uintptr_t>(param->image_mem_addr) & 3u) != 0) return SCE_JPEG_DEC_ERROR_INVALID_ADDR;
|
||||
if (!param->jpeg_mem_addr || !param->image_mem_addr) return SCE_JPEG_DEC_ERROR_INVALID_ADDR;
|
||||
if ((reinterpret_cast<std::uintptr_t>(param->image_mem_addr) & 3) != 0 && (param->pixel_format == 0 || param->pixel_format == 1 || param->pixel_format == 9 || param->pixel_format == 10)) return SCE_JPEG_DEC_ERROR_INVALID_ADDR;
|
||||
if (param->jpeg_mem_size == 0 || param->image_mem_size == 0) return SCE_JPEG_DEC_ERROR_INVALID_SIZE;
|
||||
if (param->pixel_format != PIXEL_FORMAT_R8G8B8A8 && param->pixel_format != PIXEL_FORMAT_B8G8R8A8) return SCE_JPEG_DEC_ERROR_INVALID_PARAM;
|
||||
if (param->alpha_value > 0xFF) return SCE_JPEG_DEC_ERROR_INVALID_PARAM;
|
||||
const std::uint32_t downScale = requireDownScale(__func__, param->decode_mode, param->down_scale);
|
||||
if (!validDecodeOptions(param->decode_mode, param->down_scale)) return SCE_JPEG_DEC_ERROR_INVALID_PARAM;
|
||||
if (param->pixel_format != 0 && param->pixel_format != 1 && param->pixel_format != 9 && param->pixel_format != 10 && param->pixel_format != 11) return SCE_JPEG_DEC_ERROR_INVALID_PARAM;
|
||||
if (param->alpha_value > 255) return SCE_JPEG_DEC_ERROR_INVALID_PARAM;
|
||||
requireDecodeMode(__func__, param->decode_mode);
|
||||
if (param->pixel_format != PIXEL_FORMAT_R8G8B8A8 && param->pixel_format != PIXEL_FORMAT_B8G8R8A8) throw std::runtime_error("sceJpegDecDecode: pixel format is not implemented");
|
||||
const std::uint32_t downScale = param->down_scale;
|
||||
|
||||
const std::span<const std::uint8_t> jpeg = toBytes(param->jpeg_mem_addr, param->jpeg_mem_size);
|
||||
const std::optional<Decoder::Jpeg::Header> header = Decoder::Jpeg::ParseHeader(jpeg);
|
||||
@@ -134,8 +138,12 @@ int32_t APS5_VABI sceJpegDecDecode(void* handle, const JpegDecDecodeParam* param
|
||||
|
||||
const std::uint64_t rowSize = static_cast<std::uint64_t>(outputWidth) * BYTES_PER_PIXEL;
|
||||
const std::uint64_t pitch = param->image_pitch == 0 ? rowSize : param->image_pitch;
|
||||
if (pitch < rowSize) return SCE_JPEG_DEC_ERROR_INVALID_PARAM;
|
||||
if ((outputHeight - 1) * pitch + rowSize > param->image_mem_size) return SCE_JPEG_DEC_ERROR_INVALID_SIZE;
|
||||
if (param->image_pitch != 0) {
|
||||
if (pitch < rowSize || pitch % 4 != 0 || pitch * outputHeight >= 0x80000000 || pitch * outputHeight > param->image_mem_size) return SCE_JPEG_DEC_ERROR_INVALID_PARAM;
|
||||
} else if (pitch * outputHeight > param->image_mem_size) {
|
||||
return SCE_JPEG_DEC_ERROR_INVALID_SIZE;
|
||||
}
|
||||
if (((outputWidth + 31) & ~std::uint32_t{31}) > context->maxImageWidth) return SCE_JPEG_DEC_ERROR_IMAGE_TOO_WIDE;
|
||||
|
||||
const std::optional<Decoder::Jpeg::Image> image = downScale == DOWN_SCALE_NONE ? Decoder::Jpeg::Decode(jpeg) : Decoder::Jpeg::DecodeScaled(jpeg, downScale);
|
||||
if (!image || image->width != outputWidth || image->height != outputHeight) return SCE_JPEG_DEC_ERROR_DECODE_ERROR;
|
||||
@@ -176,9 +184,12 @@ int32_t APS5_VABI sceJpegDecDelete(void* handle) {
|
||||
|
||||
int32_t APS5_VABI sceJpegDecParseHeader(const JpegDecParseParam* param, JpegDecImageInfo* imageInfo) {
|
||||
if (!param) return SCE_JPEG_DEC_ERROR_INVALID_ADDR;
|
||||
if (!param->jpeg_mem_addr || !imageInfo) return SCE_JPEG_DEC_ERROR_INVALID_ADDR;
|
||||
if (!param->jpeg_mem_addr) return SCE_JPEG_DEC_ERROR_INVALID_ADDR;
|
||||
if (param->jpeg_mem_size == 0) return SCE_JPEG_DEC_ERROR_INVALID_SIZE;
|
||||
const std::uint32_t downScale = requireDownScale(__func__, param->decode_mode, param->down_scale);
|
||||
if (!validDecodeOptions(param->decode_mode, param->down_scale)) return SCE_JPEG_DEC_ERROR_INVALID_PARAM;
|
||||
if (!imageInfo) return SCE_JPEG_DEC_ERROR_INVALID_ADDR;
|
||||
requireDecodeMode(__func__, param->decode_mode);
|
||||
const std::uint32_t downScale = param->down_scale;
|
||||
const std::optional<Decoder::Jpeg::Header> header = Decoder::Jpeg::ParseHeader(toBytes(param->jpeg_mem_addr, param->jpeg_mem_size));
|
||||
if (!header) return SCE_JPEG_DEC_ERROR_INVALID_DATA;
|
||||
fillImageInfo(*header, downScale, imageInfo);
|
||||
|
||||
@@ -83,7 +83,7 @@ int main() {
|
||||
Require(sceJpegDecParseHeader(&emptyParam, &info) == invalidAddr);
|
||||
for (const std::uint16_t downScale : {std::uint16_t{0}, std::uint16_t{3}, std::uint16_t{16}}) {
|
||||
const JpegDecParseParam unknownScaleParse{jpeg.data(), static_cast<std::uint32_t>(jpeg.size()), 0, downScale};
|
||||
Require(ThrowsRuntimeError([&] { sceJpegDecParseHeader(&unknownScaleParse, &info); }));
|
||||
Require(sceJpegDecParseHeader(&unknownScaleParse, &info) == invalidParam);
|
||||
}
|
||||
const JpegDecParseParam otherModeParse{jpeg.data(), static_cast<std::uint32_t>(jpeg.size()), 1, 1};
|
||||
Require(ThrowsRuntimeError([&] { sceJpegDecParseHeader(&otherModeParse, &info); }));
|
||||
@@ -147,7 +147,16 @@ int main() {
|
||||
Require(sceJpegDecDecode(handle, &badFormat, &info) == invalidParam);
|
||||
JpegDecDecodeParam unknownScale = decodeParam;
|
||||
unknownScale.down_scale = 3;
|
||||
Require(ThrowsRuntimeError([&] { sceJpegDecDecode(handle, &unknownScale, &info); }));
|
||||
Require(sceJpegDecDecode(handle, &unknownScale, &info) == invalidParam);
|
||||
JpegDecDecodeParam invalidOptions = decodeParam;
|
||||
invalidOptions.alpha_value = 256;
|
||||
Require(sceJpegDecDecode(handle, &invalidOptions, &info) == invalidParam);
|
||||
invalidOptions = decodeParam;
|
||||
invalidOptions.decode_mode = 2;
|
||||
Require(sceJpegDecDecode(handle, &invalidOptions, &info) == invalidParam);
|
||||
invalidOptions = decodeParam;
|
||||
invalidOptions.image_mem_addr = image.data() + 1;
|
||||
Require(sceJpegDecDecode(handle, &invalidOptions, &info) == invalidAddr);
|
||||
std::vector<std::uint8_t> invalidAlphaImage(7 * 5 * 4, 0xCC);
|
||||
JpegDecDecodeParam invalidAlpha = DecodeParam(jpeg, invalidAlphaImage, 7 * 4);
|
||||
invalidAlpha.alpha_value = 0x100;
|
||||
@@ -175,7 +184,7 @@ int main() {
|
||||
}
|
||||
|
||||
constexpr std::uint32_t pitch = 7 * 4 + 8;
|
||||
std::vector<std::uint8_t> bgra(4 * pitch + 7 * 4, 0xEE);
|
||||
std::vector<std::uint8_t> bgra(5 * pitch, 0xEE);
|
||||
JpegDecDecodeParam bgraParam = DecodeParam(jpeg, bgra, pitch);
|
||||
bgraParam.pixel_format = 1;
|
||||
bgraParam.alpha_value = 0x77;
|
||||
@@ -192,6 +201,9 @@ int main() {
|
||||
}
|
||||
|
||||
const std::vector<std::uint8_t> large = TestJpeg(37, 21, 3);
|
||||
std::vector<std::uint8_t> wideOutput(37 * 21 * 4);
|
||||
JpegDecDecodeParam wideParam = DecodeParam(large, wideOutput, 0);
|
||||
Require(sceJpegDecDecode(handle, &wideParam, nullptr) == static_cast<std::int32_t>(0x80650006));
|
||||
const std::vector<std::uint8_t> largeGray = TestJpeg(19, 11, 1);
|
||||
for (const auto* source : {&large, &largeGray}) {
|
||||
const std::optional<Decoder::Jpeg::Header> sourceHeader = Decoder::Jpeg::ParseHeader(*source);
|
||||
@@ -215,7 +227,7 @@ int main() {
|
||||
}
|
||||
|
||||
const std::uint32_t paddedPitch = width * 4 + 4;
|
||||
std::vector<std::uint8_t> padded((height - 1) * paddedPitch + width * 4, 0xEE);
|
||||
std::vector<std::uint8_t> padded(height * paddedPitch, 0xEE);
|
||||
JpegDecDecodeParam paddedParam = DecodeParam(*source, padded, paddedPitch);
|
||||
paddedParam.down_scale = downScale;
|
||||
paddedParam.pixel_format = 1;
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
### Unknown function info
|
||||
|
||||
- [sceJpegDecDecodeWithInputControl](../../../core/libs/prx/libSceJpegDec/Export.cpp) (libSceJpegDec) - its name and NID are in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceJpegDec.c), but no credible prototype was found. It is declared without parameters solely to resolve known imports; this is not an ABI claim
|
||||
- [libSceJpegDec](../../../core/libs/prx/libSceJpegDec/Export.cpp) (libSceJpegDec) - structure layouts follow [OpenOrbis `jpeg.h`](https://github.com/OpenOrbis/OpenOrbis-PS4-Toolchain/blob/0a1aaf9dd4a92695538bdeb09fb056d06dd11725/include/orbis/_types/jpeg.h#L13-L72) and [SharpProspero `JpegDec.cs`](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Image/JpegDec.cs#L20-L117); attributes 1 and 2 are the values [SharpProspero's `JpegImage.cs`](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Graphics/JpegImage.cs#L64-L74) passes. Error codes, creation validation, 32-byte handle alignment and the attribute/width-dependent work-memory size are confirmed by the native exports. Image info always reports color space 3, suitable CSC attribute 1 and zero component ids and sampling factors; the console's values, including which images it reports attribute 2 for, are unknown. The coefficient memory size is 0 because the host decoder needs no coefficient buffer; the size the console reports for progressive images is unknown. `max_image_width` is not checked, and the packed `width << 16 | height` decode result is confirmed by the native export
|
||||
- [libSceJpegDec](../../../core/libs/prx/libSceJpegDec/Export.cpp) (libSceJpegDec) - structure layouts follow [OpenOrbis `jpeg.h`](https://github.com/OpenOrbis/OpenOrbis-PS4-Toolchain/blob/0a1aaf9dd4a92695538bdeb09fb056d06dd11725/include/orbis/_types/jpeg.h#L13-L72) and [SharpProspero `JpegDec.cs`](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Image/JpegDec.cs#L20-L117); attributes 1 and 2 are the values [SharpProspero's `JpegImage.cs`](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Graphics/JpegImage.cs#L64-L74) passes. Error codes, creation validation, 32-byte handle alignment and the attribute/width-dependent work-memory size are confirmed by the native exports. Image info always reports color space 3, suitable CSC attribute 1 and zero component ids and sampling factors; the console's values, including which images it reports attribute 2 for, are unknown. The coefficient memory size is 0 because the host decoder needs no coefficient buffer; the size the console reports for progressive images is unknown. the output-width limit, aligned output address, pitch, alpha and option validation are confirmed by native checks, and the packed `width << 16 | height` decode result is confirmed by the native export
|
||||
|
||||
### Functional
|
||||
|
||||
- [libSceJpegDec](../../../core/libs/prx/libSceJpegDec/Export.cpp) (libSceJpegDec) - decode modes other than 0 and down scales other than 1, 2, 4 and 8 throw, as does a down scale of 2, 4 or 8 on a CMYK or YCCK image, which libjpeg-turbo cannot decode to RGB. The down scale factors are the ones [SharpProspero `JpegDec.cs`](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Image/JpegDec.cs#L57-L58) documents; the output size is the source size divided by the factor and rounded up, and the pixels come from libjpeg-turbo's scaled inverse DCT. The console's rounding, pixel values and the packed decode result for a down-scaled image (the output size here) are unverified. In `sceJpegDecDecode`, rejecting an output `image_mem_addr` not aligned to 4 bytes and rejecting an `alpha_value` above 0xFF follow libScePngDec validation and are not verified from a libSceJpegDec binary disassembly
|
||||
- [libSceJpegDec](../../../core/libs/prx/libSceJpegDec/Export.cpp) (libSceJpegDec) - decode mode 1 and pixel formats 9, 10 and 11 throw; invalid modes and down scales return the native invalid-parameter error, as does a down scale of 2, 4 or 8 on a CMYK or YCCK image, which libjpeg-turbo cannot decode to RGB. The down scale factors are the ones [SharpProspero `JpegDec.cs`](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Image/JpegDec.cs#L57-L58) documents; the output size is the source size divided by the factor and rounded up, and the pixels come from libjpeg-turbo's scaled inverse DCT. The native ceiling division and packed scaled output dimensions are confirmed; exact decoded pixel values remain unverified
|
||||
Reference in new issue
Block a user