Fix out-of-bounds reads in PGF font parsing

PGF::ReadPtr walked four length-prefixed tables and computed table sizes
before any bounds check, and used signed 32-bit size math that could
overflow, allowing a crafted font to read past the input buffer.

- Validate the total size of all tables up front using 64-bit math.
- Check the rev3 extra header fits before reading it.
- Cap charPointerLength/charMapLength/shadowMapLength to avoid absurd
  allocations.
- Bounds-check glyph data offsets before reading each glyph.

Also throw in a warning fix
This commit is contained in:
Henrik Rydgård committed 2026-08-01 11:57:27 +02:00
1 parent a8195e7ca6
commit 788150c28e
2 files changed
+45 -13

No files matched your search

+40 -7
View File
@@ -187,12 +187,37 @@ bool PGF::ReadPtr(const u8 *ptr, size_t dataSize) {
fileName = header.fontName;
if (header.revision == 3) {
if (dataSize < sizeof(header) + sizeof(rev3extra)) {
return false;
}
memcpy(&rev3extra, ptr, sizeof(rev3extra));
rev3extra.compCharMapLength1 &= 0xFFFF;
rev3extra.compCharMapLength2 &= 0xFFFF;
ptr += sizeof(rev3extra);
}
// Validate that all tables fit in the input buffer before reading any of
// them. Use 64-bit arithmetic: the original 32-bit signed size math could
// overflow for crafted lengths.
const u64 headerSize = (u64)(ptr - startPtr);
const u64 tablesSize = ((u64)header.dimTableLength + header.xAdjustTableLength + header.yAdjustTableLength + header.advanceTableLength) * 8;
const u64 shadowCharMapSize = (((u64)header.shadowMapLength * header.shadowMapBpe + 31) & ~31ull) / 8;
const u64 compTableSize = header.revision == 3 ? ((u64)rev3extra.compCharMapLength1 + rev3extra.compCharMapLength2) * 4 : 0;
const u64 charMapSize = (((u64)header.charMapLength * header.charMapBpe + 31) & ~31ull) / 8;
const u64 charPointerSize = (((u64)header.charPointerLength * header.charPointerBpe + 31) & ~31ull) / 8;
// Also cap the lengths so a crafted font can't force absurd allocations
// or loops downstream. Real PGF fonts are tiny.
if (header.charPointerLength < 0 || header.charPointerLength > 0x100000 ||
header.charMapLength < 0 || header.charMapLength > 0x100000 ||
header.shadowMapLength < 0 || header.shadowMapLength > 0x10000) {
return false;
}
if (headerSize + tablesSize + shadowCharMapSize + compTableSize + charMapSize + charPointerSize > dataSize) {
return false;
}
const u32_le *wptr = (const u32_le *)ptr;
dimensionTable[0].resize(header.dimTableLength);
dimensionTable[1].resize(header.dimTableLength);
@@ -224,7 +249,6 @@ bool PGF::ReadPtr(const u8 *ptr, size_t dataSize) {
const u8 *uptr = (const u8 *)wptr;
int shadowCharMapSize = ((header.shadowMapLength * header.shadowMapBpe + 31) & ~31) / 8;
const u8 *shadowCharMap = uptr;
uptr += shadowCharMapSize;
@@ -251,11 +275,9 @@ bool PGF::ReadPtr(const u8 *ptr, size_t dataSize) {
uptr = (const u8 *)sptr;
int charMapSize = ((header.charMapLength * header.charMapBpe + 31) & ~31) / 8;
const u8 *charMap = uptr;
uptr += charMapSize;
int charPointerSize = (((header.charPointerLength * header.charPointerBpe + 31) & ~31) / 8);
const u8 *charPointerTable = uptr;
uptr += charPointerSize;
@@ -290,9 +312,17 @@ bool PGF::ReadPtr(const u8 *ptr, size_t dataSize) {
std::vector<int> charPointers = getTable(charPointerTable, header.charPointerBpe, glyphs.size());
std::vector<int> shadowMap = getTable(shadowCharMap, header.shadowMapBpe, (s32)header.shadowMapLength);
// Pregenerate glyphs.
// Pregenerate glyphs. charPointers come from the (attacker-controlled)
// char pointer table, so their offsets into fontData must be validated.
const size_t fontDataBits = (size_t)fontDataSize * 8;
for (size_t i = 0; i < glyphs.size(); i++) {
ReadCharGlyph(fontData, charPointers[i] * 4 * 8 /* ??? */, glyphs[i]);
if (charPointers[i] < 0)
continue;
size_t charPtr = (size_t)charPointers[i] * 4 * 8;
// Leave a margin for the glyph header reads (a few hundred bits).
if (charPtr + 1024 > fontDataBits)
continue; // Out of range; leave the glyph empty.
ReadCharGlyph(fontData, charPtr, glyphs[i]);
}
// And shadow glyphs.
@@ -300,9 +330,12 @@ bool PGF::ReadPtr(const u8 *ptr, size_t dataSize) {
size_t shadowId = glyphs[i].shadowID;
if (shadowId < shadowMap.size()) {
size_t charId = shadowMap[shadowId];
if (charId < shadowGlyphs.size()) {
if (charId < shadowGlyphs.size() && charPointers[charId] >= 0) {
size_t charPtr = (size_t)charPointers[charId] * 4 * 8;
if (charPtr + 1024 > fontDataBits)
continue; // Out of range.
// TODO: check for pre existing shadow glyph
ReadShadowGlyph(fontData, charPointers[charId] * 4 * 8 /* ??? */, shadowGlyphs[charId]);
ReadShadowGlyph(fontData, charPtr, shadowGlyphs[charId]);
}
}
}
+5 -6
View File
@@ -2625,15 +2625,14 @@ int sceKernelReleaseWaitThread(SceUID threadID) {
if (!t) {
return hleLogError(Log::sceKernel, error, "bad thread ID");
} else {
if (!t->isWaiting())
return hleLogError(Log::sceKernel, SCE_KERNEL_ERROR_NOT_WAIT);
if (t->nt.waitType == WAITTYPE_HLEDELAY)
{
if (!t->isWaiting()) {
return hleLogInfo(Log::sceKernel, SCE_KERNEL_ERROR_NOT_WAIT);
}
if (t->nt.waitType == WAITTYPE_HLEDELAY) {
WARN_LOG_REPORT_ONCE(rwt_delay, Log::sceKernel, "sceKernelReleaseWaitThread(): Refusing to wake HLE-delayed thread, right thing to do?");
return hleNoLog(SCE_KERNEL_ERROR_NOT_WAIT);
}
if (t->nt.waitType == WAITTYPE_MODULE)
{
if (t->nt.waitType == WAITTYPE_MODULE) {
WARN_LOG_REPORT_ONCE(rwt_sm, Log::sceKernel, "sceKernelReleaseWaitThread(): Refusing to wake start_module thread, right thing to do?");
return hleNoLog(SCE_KERNEL_ERROR_NOT_WAIT);
}