Merge pull request #22939 from hrydgard/parser-hardening

Harden file format parsers against malformed input
This commit is contained in:
Henrik Rydgård authored and GitHub committed 2026-11-05 16:29:51 +01:00
commit c338f76ec9
56 files changed
+729 -222

No files matched your search

+7 -3
View File
@@ -54,7 +54,7 @@ bool compress_string(const std::string& str, std::string *dest, int compressionl
}
/** Decompress an STL string using zlib and return the original data. */
bool decompress_string(const std::string& str, std::string *dest) {
bool decompress_string(const std::string& str, std::string *dest, size_t maxSize) {
if (!str.size())
return false;
@@ -85,7 +85,11 @@ bool decompress_string(const std::string& str, std::string *dest) {
outstring.append(outbuffer,
zs.total_out - outstring.size());
}
if (outstring.size() > maxSize) {
inflateEnd(&zs);
ERROR_LOG(Log::IO, "Decompressed data is larger than %d bytes, giving up", (int)maxSize);
return false;
}
} while (ret == Z_OK);
inflateEnd(&zs);
@@ -95,6 +99,6 @@ bool decompress_string(const std::string& str, std::string *dest) {
return false;
}
*dest = outstring;
*dest = std::move(outstring);
return true;
}
+2 -1
View File
@@ -4,4 +4,5 @@
// inflate/deflate convenience wrapper. Uses zlib.
bool compress_string(const std::string& str, std::string *dest, int compressionlevel = 9);
bool decompress_string(const std::string& str, std::string *dest);
// Fails if the output would be larger than maxSize (a small input can expand enormously).
bool decompress_string(const std::string& str, std::string *dest, size_t maxSize = 256 * 1024 * 1024);
+7
View File
@@ -110,6 +110,12 @@ int u8_charnum(const char *s, int offset)
uint32_t u8_nextchar(const char *s, int *index, size_t size) {
uint32_t ch = 0;
_dbg_assert_(*index >= 0 && *index < 100000000);
// At (or past) the end, there's nothing to read. Leave the index at the end so loops waiting
// for end() finish.
if (*index < 0 || (size_t)*index >= size) {
*index = (int)size;
return 0;
}
int sz = 0;
int i = *index;
do {
@@ -138,6 +144,7 @@ void u8_dec(const char *s, int *i) {
bool AnyEmojiInString(std::string_view str, size_t byteCount) {
int i = 0;
byteCount = std::min(byteCount, str.size());
while (i < byteCount) {
uint32_t c = u8_nextchar(str.data(), &i, str.size());
if (CodepointIsProbablyEmoji(c)) {
+15 -2
View File
@@ -1,3 +1,5 @@
#include <climits>
#include "Common/Data/Format/JSONReader.h"
#include "Common/File/VFS/VFS.h"
#include "Common/File/Path.h"
@@ -121,20 +123,31 @@ double JsonGet::getFloat(const char *child_name, double default_value) const {
return val->value.toNumber();
}
// Casting a double that's out of range (or NaN) to int is undefined.
static int ClampToInt(double d) {
if (!(d >= (double)INT_MIN)) {
return d != d ? 0 : INT_MIN;
}
if (d > (double)INT_MAX) {
return INT_MAX;
}
return (int)d;
}
int JsonGet::getInt(const char *child_name) const {
const JsonNode *val = get(child_name, JSON_NUMBER);
if (!val) {
ERROR_LOG(Log::IO, "Number '%s' missing from node", child_name);
return 0;
}
return (int)val->value.toNumber();
return ClampToInt(val->value.toNumber());
}
int JsonGet::getInt(const char *child_name, int default_value) const {
const JsonNode *val = get(child_name, JSON_NUMBER);
if (!val)
return default_value;
return (int)val->value.toNumber();
return ClampToInt(val->value.toNumber());
}
bool JsonGet::getBool(const char *child_name) const {
+21 -4
View File
@@ -23,13 +23,28 @@ int pngLoad(const char *file, int *pwidth, int *pheight, unsigned char **image_d
*image_data_ptr = nullptr;
return 0;
}
if (png.width == 0 || png.height == 0 || png.width > 8192 || png.height > 8192) {
WARN_LOG(Log::IO, "pngLoad: Bad size %dx%d (%s)", (int)png.width, (int)png.height, file);
png_image_free(&png);
*image_data_ptr = nullptr;
return 0;
}
*pwidth = png.width;
*pheight = png.height;
png.format = PNG_FORMAT_RGBA;
int stride = PNG_IMAGE_ROW_STRIDE(png);
*image_data_ptr = (unsigned char *)malloc(PNG_IMAGE_SIZE(png));
png_image_finish_read(&png, NULL, *image_data_ptr, stride, NULL);
if (!*image_data_ptr) {
png_image_free(&png);
return 0;
}
if (!png_image_finish_read(&png, NULL, *image_data_ptr, stride, NULL)) {
WARN_LOG(Log::IO, "pngLoad: %s (%s)", png.message, file);
free(*image_data_ptr);
*image_data_ptr = nullptr;
return 0;
}
return 1;
}
@@ -50,11 +65,13 @@ struct PngReadContext {
int pngLoadPtr(const unsigned char *input_ptr, size_t input_len, int *pwidth, int *pheight, unsigned char **image_data_ptr, int maxWidth, int maxHeight) {
png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, pngErrorHandler, pngWarningHandler);
if (!png) {
// The error handler below frees whatever this points to.
*image_data_ptr = nullptr;
if (input_len == 0) {
return 0;
}
if (input_len == 0) {
png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, pngErrorHandler, pngWarningHandler);
if (!png) {
return 0;
}
+8 -3
View File
@@ -63,7 +63,9 @@ int LoadZIMPtr(const uint8_t *zim, size_t datasize, int *width, int *height, int
memcpy(height, zim + 8, 4);
memcpy(flags, zim + 12, 4);
if (*width <= 0 || *height <= 0) {
// Nothing we use is anywhere near this, and the data is often compressed, so the
// size is otherwise only bounded by how much memory a decompression bomb can claim.
if (*width <= 0 || *height <= 0 || *width > 8192 || *height > 8192) {
ERROR_LOG(Log::IO, "Invalid ZIM dimensions %i x %i", *width, *height);
return 0;
}
@@ -147,9 +149,12 @@ int LoadZIMPtr(const uint8_t *zim, size_t datasize, int *width, int *height, int
*image = 0;
return 0;
}
if (outlen != total_data_size) {
// Shouldn't happen if return value was Z_OK.
if (outlen != (long)total_data_size) {
// The stream ended early, leaving the rest of the image uninitialized.
ERROR_LOG(Log::IO, "Wrong size data in ZIM: %i vs %i", (int)outlen, (int)total_data_size);
free(*image);
*image = 0;
return 0;
}
} else if (*flags & ZIM_ZSTD_COMPRESSED) {
size_t outlen = ZSTD_decompress(*image, total_data_size, zim + 16, payload_size);
+4 -1
View File
@@ -169,7 +169,10 @@ StringWriter &StringWriter::F(const char *format, ...) {
va_list args;
va_start(args, format);
int wouldHaveBeenWritten = vsnprintf(p_, remainder, format, args);
p_ += std::min((int)remainder, wouldHaveBeenWritten);
va_end(args);
// -1 on encoding errors, which mustn't move p_ backwards.
if (wouldHaveBeenWritten > 0) {
p_ += std::min((int)remainder, wouldHaveBeenWritten);
}
return *this;
}
+17 -4
View File
@@ -24,13 +24,17 @@ static std::string ToLowerASCII(std::string_view str) {
return lower;
}
void DirectoryReader::IndexDirectory(const Path &dir, const std::string &prefix) {
void DirectoryReader::IndexDirectory(const Path &dir, const std::string &prefix, int depth) {
// A symlink loop would otherwise recurse until the stack runs out.
if (depth > 32) {
return;
}
std::vector<File::FileInfo> files;
File::GetFilesInDir(dir, &files);
for (const File::FileInfo &file : files) {
const std::string relative = prefix + file.name;
if (file.isDirectory) {
IndexDirectory(file.fullName, relative + "/");
IndexDirectory(file.fullName, relative + "/", depth + 1);
} else {
lowerIndex_[ToLowerASCII(relative)] = relative;
}
@@ -44,7 +48,7 @@ bool DirectoryReader::FindIgnoringCase(std::string_view path, std::string *actua
std::lock_guard<std::mutex> guard(indexLock_);
if (!indexBuilt_) {
// Only done on the first miss, so a directory where every name matches never pays for it.
IndexDirectory(path_, "");
IndexDirectory(path_, "", 0);
indexBuilt_ = true;
INFO_LOG(Log::IO, "Indexed %d files in %s to find names that differ in case", (int)lowerIndex_.size(), path_.c_str());
}
@@ -134,13 +138,22 @@ void DirectoryReader::ReleaseFile(VFSFileReference *vfsReference) {
VFSOpenFile *DirectoryReader::OpenFileForRead(VFSFileReference *vfsReference, size_t *size) {
DirectoryReaderFileReference *reference = (DirectoryReaderFileReference *)vfsReference;
// On some platforms, directories can be opened as files. Their size is meaningless.
if (File::IsDirectory(reference->path)) {
return nullptr;
}
FILE *file = File::OpenCFile(reference->path, "rb");
if (!file) {
return nullptr;
}
File::Fseek(file, 0, SEEK_END);
*size = File::Ftell(file);
const int64_t fileSize = File::Ftell(file);
File::Fseek(file, 0, SEEK_SET);
if (fileSize < 0) {
fclose(file);
return nullptr;
}
*size = (size_t)fileSize;
DirectoryReaderOpenFile *openFile = new DirectoryReaderOpenFile();
openFile->file = file;
return openFile;
+1 -1
View File
@@ -36,7 +36,7 @@ public:
private:
// Returns the path (relative to path_) of a file whose name matches path except for case.
bool FindIgnoringCase(std::string_view path, std::string *actualPath);
void IndexDirectory(const Path &dir, const std::string &prefix);
void IndexDirectory(const Path &dir, const std::string &prefix, int depth);
Path path_;
bool ignoreCase_ = false;
+26 -7
View File
@@ -16,8 +16,10 @@
#include "Common/StringUtils.h"
// Sanity cap on a single zip entry's declared uncompressed size, so a corrupt or
// malicious zip can't drive an implausible (or, near UINT64_MAX, wrapping) allocation.
static constexpr uint64_t MAX_ZIP_ENTRY_SIZE = 1ULL << 32; // 4GB, generous for any real asset.
// malicious zip can't drive an implausible allocation (which also has to fit in a
// 32-bit size_t). Entries are read whole into memory; the largest real asset, an
// uncompressed 8192x8192 replacement texture with mips, is under 512MB.
static constexpr uint64_t MAX_ZIP_ENTRY_SIZE = 512ULL * 1024 * 1024;
ZipContainer::ZipContainer() noexcept : sourceData_(nullptr), zip_(nullptr) {}
@@ -186,8 +188,13 @@ uint8_t *ZipFileReader::ReadFile(std::string_view path, size_t *size) {
return 0;
}
uint8_t *contents = new uint8_t[zstat.size + 1];
zip_fread(file, contents, zstat.size);
const zip_int64_t bytesRead = zip_fread(file, contents, zstat.size);
zip_fclose(file);
if (bytesRead < 0 || (uint64_t)bytesRead != zstat.size) {
ERROR_LOG(Log::IO, "Error reading %s from ZIP (%lld of %llu bytes)", temp_path.c_str(), (long long)bytesRead, (unsigned long long)zstat.size);
delete[] contents;
return 0;
}
contents[zstat.size] = 0;
*size = zstat.size;
@@ -402,6 +409,13 @@ VFSOpenFile *ZipFileReader::OpenFileForRead(VFSFileReference *vfsReference, size
delete openFile;
return nullptr;
}
// Callers allocate the returned size.
if (zstat.size > MAX_ZIP_ENTRY_SIZE) {
ERROR_LOG(Log::IO, "Zip entry %d claims an implausible size (%llu), refusing to open", reference->zi, (unsigned long long)zstat.size);
lock_.unlock();
delete openFile;
return nullptr;
}
openFile->zf = zip_fopen_index(zip_file_, reference->zi, 0);
if (!openFile->zf) {
@@ -429,15 +443,20 @@ void ZipFileReader::Rewind(VFSOpenFile *vfsOpenFile) {
size_t ZipFileReader::Read(VFSOpenFile *vfsOpenFile, void *buffer, size_t length) {
ZipFileReaderOpenFile *file = (ZipFileReaderOpenFile *)vfsOpenFile;
_assert_(file);
_dbg_assert_(file->zf != nullptr);
return zip_fread(file->zf, buffer, length);
if (!file->zf) {
return 0;
}
// -1 on errors, like a bad CRC or a corrupt stream.
const zip_int64_t bytesRead = zip_fread(file->zf, buffer, length);
return bytesRead < 0 ? 0 : (size_t)bytesRead;
}
void ZipFileReader::CloseFile(VFSOpenFile *vfsOpenFile) {
ZipFileReaderOpenFile *file = (ZipFileReaderOpenFile *)vfsOpenFile;
_assert_(file);
_dbg_assert_(file->zf != nullptr);
zip_fclose(file->zf);
if (file->zf) {
zip_fclose(file->zf);
}
file->zf = nullptr;
vfsOpenFile = nullptr;
lock_.unlock();
+28
View File
@@ -94,6 +94,18 @@ bool TempImage::LoadTextureLevelsFromFileData(const uint8_t *data, size_t size,
switch (typeSuggestion) {
case ImageFileType::ZIM:
numLevels = LoadZIMPtr((const uint8_t *)data, size, width, height, &zimFlags, levels);
if (numLevels <= 0) {
numLevels = 0;
return false;
}
// Everything below treats the data as four bytes per pixel.
if ((zimFlags & ZIM_FORMAT_MASK) != ZIM_RGBA8888 || width[0] > maxWidth || height[0] > maxHeight) {
ERROR_LOG(Log::IO, "Unsupported ZIM: format %d, %dx%d", zimFlags & ZIM_FORMAT_MASK, width[0], height[0]);
free(levels[0]);
levels[0] = nullptr;
numLevels = 0;
return false;
}
fmt = ZimToT3DFormat(zimFlags & ZIM_FORMAT_MASK);
ConvertRGBA8888ToPremulAlpha((u32 *)levels[0], (const u32 *)levels[0], width[0] * height[0]);
break;
@@ -117,6 +129,22 @@ bool TempImage::LoadTextureLevelsFromFileData(const uint8_t *data, size_t size,
case ImageFileType::JPEG:
{
int actual_components = 0;
if (size > INT_MAX) {
return false;
}
// Check the size before decoding.
{
jpgd::jpeg_decoder_mem_stream stream(data, (jpgd::uint)size);
jpgd::jpeg_decoder decoder(&stream);
if (decoder.get_error_code() != jpgd::JPGD_SUCCESS) {
ERROR_LOG(Log::IO, "Bad JPEG header");
return false;
}
if (decoder.get_width() <= 0 || decoder.get_height() <= 0 || decoder.get_width() > maxWidth || decoder.get_height() > maxHeight) {
ERROR_LOG(Log::IO, "JPEG too large: %dx%d (max %dx%d)", decoder.get_width(), decoder.get_height(), maxWidth, maxHeight);
return false;
}
}
unsigned char *jpegBuf = jpgd::decompress_jpeg_image_from_memory(data, (int)size, &width[0], &height[0], &actual_components, 4);
if (jpegBuf) {
numLevels = 1;
+1 -1
View File
@@ -117,7 +117,7 @@ void TextDrawer::DrawString(DrawBuffer &target, const FontStyle &style, float sc
} else {
DataFormat texFormat;
// Pick between the supported formats, of which at least one is supported on each platform. Prefer R8 (but only if swizzle is supported)
bool emoji = SupportsColorEmoji() && AnyEmojiInString(str.data(), str.length());
bool emoji = SupportsColorEmoji() && AnyEmojiInString(str, str.length());
if (emoji) {
texFormat = Draw::DataFormat::R8G8B8A8_UNORM;
} else if ((draw_->GetDataFormatSupport(Draw::DataFormat::R8_UNORM) & Draw::FMT_TEXTURE) != 0 && draw_->GetDeviceCaps().textureSwizzleSupported) {
+9
View File
@@ -32,6 +32,15 @@ void DoDeque(PointerWrap &p, std::deque<T> &x, T &default_val) {
return;
}
}
// See DoVector.
if (p.mode == PointerWrap::MODE_READ && sizeof(T) > SerializeMinElemSize<T>()) {
x.clear();
for (u32 i = 0; i < deq_size && !p.Failed(); i++) {
x.push_back(default_val);
Do(p, x.back());
}
return;
}
x.resize(deq_size, default_val);
u32 i;
for (i = 0; i < deq_size; i++)
+13
View File
@@ -18,6 +18,7 @@
#pragma once
// Templates for save state serialization. See Serializer.h.
#include <algorithm>
#include <string>
#include <type_traits>
#include "Common/Serialize/Serializer.h"
@@ -134,6 +135,18 @@ void DoVector(PointerWrap &p, std::vector<T> &x, T &default_val) {
return;
}
}
// Elements that aren't stored raw can serialize to a few bytes but be much bigger in memory, so
// the check above still lets a corrupt state ask for gigabytes. Grow as the data arrives instead.
if (p.mode == PointerWrap::MODE_READ && sizeof(T) > SerializeMinElemSize<T>()) {
x.clear();
for (u32 done = 0; done < vec_size && !p.Failed(); ) {
const u32 n = std::min(vec_size - done, (u32)1024);
x.resize(done + n, default_val);
DoArray(p, &x[done], n);
done += n;
}
return;
}
if (vec_size != x.size())
x.resize(vec_size, default_val);
if (vec_size > 0)
+9
View File
@@ -32,6 +32,15 @@ void DoList(PointerWrap &p, std::list<T> &x, T &default_val) {
return;
}
}
// See DoVector.
if (p.mode == PointerWrap::MODE_READ && sizeof(T) > SerializeMinElemSize<T>()) {
x.clear();
for (u32 i = 0; i < list_size && !p.Failed(); i++) {
x.push_back(default_val);
Do(p, x.back());
}
return;
}
x.resize(list_size, default_val);
for (T &elem : x)
+21 -13
View File
@@ -503,18 +503,22 @@ CChunkFileReader::Error CChunkFileReader::LoadFile(const Path &filename, std::st
if (header.Compress) {
// Sanity cap on the decompressed size to avoid a giant allocation from
// an attacker-controlled header field. Real savestates are well under this.
if (header.UncompressedSize > 0x40000000) {
// an attacker-controlled header field. Real savestates (even with 64MB of
// RAM) are well under this.
if (header.UncompressedSize > 0x10000000) {
ERROR_LOG(Log::SaveState, "ChunkReader: UncompressedSize too large: %u", header.UncompressedSize);
return ERROR_BAD_FILE;
}
}
if (SerializeCompressType(header.Compress) == SerializeCompressType::ZSTD) {
u8 *uncomp_buffer = new u8[header.UncompressedSize];
u8 *uncomp_buffer = (u8 *)malloc(header.UncompressedSize);
if (!uncomp_buffer) {
return ERROR_BAD_FILE;
}
if (!DecompressZstdFromFile(pFile, header.ExpectedSize, uncomp_buffer, header.UncompressedSize)) {
ERROR_LOG(Log::SaveState, "ChunkReader: Failed to decompress file");
delete [] uncomp_buffer;
free(uncomp_buffer);
return ERROR_BAD_FILE;
}
_buffer = uncomp_buffer;
@@ -525,16 +529,20 @@ CChunkFileReader::Error CChunkFileReader::LoadFile(const Path &filename, std::st
// Uncompressed, or snappy (older states).
sz = header.ExpectedSize;
u8 *buffer = new u8[sz];
if (!pFile.ReadBytes(buffer, sz))
u8 *buffer = (u8 *)malloc(sz);
if (!buffer || !pFile.ReadBytes(buffer, sz))
{
ERROR_LOG(Log::SaveState, "ChunkReader: Error reading file");
delete [] buffer;
free(buffer);
return ERROR_BAD_FILE;
}
if (header.Compress) {
u8 *uncomp_buffer = new u8[header.UncompressedSize];
u8 *uncomp_buffer = (u8 *)malloc(header.UncompressedSize);
if (!uncomp_buffer) {
free(buffer);
return ERROR_BAD_FILE;
}
size_t uncomp_size = header.UncompressedSize;
bool success = false;
if (SerializeCompressType(header.Compress) == SerializeCompressType::SNAPPY) {
@@ -545,19 +553,19 @@ CChunkFileReader::Error CChunkFileReader::LoadFile(const Path &filename, std::st
}
if (!success) {
ERROR_LOG(Log::SaveState, "ChunkReader: Failed to decompress file");
delete [] uncomp_buffer;
delete [] buffer;
free(uncomp_buffer);
free(buffer);
return ERROR_BAD_FILE;
}
if ((u32)uncomp_size != header.UncompressedSize) {
ERROR_LOG(Log::SaveState, "Size mismatch: file: %u calc: %u", header.UncompressedSize, (u32)uncomp_size);
delete [] uncomp_buffer;
delete [] buffer;
free(uncomp_buffer);
free(buffer);
return ERROR_BAD_FILE;
}
_buffer = uncomp_buffer;
sz = uncomp_size;
delete [] buffer;
free(buffer);
} else {
_buffer = buffer;
}
+2 -1
View File
@@ -303,7 +303,7 @@ public:
if (error == ERROR_NONE) {
failureReason->clear();
error = LoadPtr(ptr, sz, _class, failureReason);
delete [] ptr;
free(ptr);
INFO_LOG(Log::SaveState, "ChunkReader: Done loading '%s'", filename.c_str());
} else {
WARN_LOG(Log::SaveState, "ChunkReader: Error found during load of '%s'", filename.c_str());
@@ -359,6 +359,7 @@ public:
// The file half of Load() and Save(), for doing it away from the state itself. LoadFile hands back
// a new[]'d buffer; SaveFile takes ownership of a malloc'd one (as from MeasureAndSavePtr).
// The buffer is allocated with malloc (so the allocation can fail cleanly), free it with free().
static Error LoadFile(const Path &filename, std::string *gitVersion, u8 *&buffer, size_t &sz);
// Reads only the first size bytes of the state, decompressing no more than that. Not for snappy
// compressed (old) states.
+5
View File
@@ -1469,8 +1469,13 @@ void CWCheatEngine::Run() {
// InterpretNextOp and ExecuteOp move i.
for (size_t i = 0; i < cheat.lines.size(); ) {
const size_t start = i;
CheatOperation op = InterpretNextCwCheat(cheat, i);
ExecuteOp(op, cheat, i);
// A skip count near 2^32 wraps i on 32-bit hosts, which would run the same code forever.
if (i <= start) {
break;
}
}
}
}
+70 -27
View File
@@ -16,6 +16,7 @@
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
#include <algorithm>
#include <cstdint>
#include <cstring>
#include <map>
@@ -83,19 +84,20 @@ public:
return Fail();
}
int64_t SLEB() {
int64_t result = 0;
// Unsigned, so shifting into (and past) the sign bit is defined.
uint64_t result = 0;
int shift = 0;
for (int i = 0; i < 10; i++) {
const uint8_t b = U8();
if (!ok_)
return 0;
if (shift < 64)
result |= (int64_t)(b & 0x7f) << shift;
result |= (uint64_t)(b & 0x7f) << shift;
shift += 7;
if (!(b & 0x80)) {
if (shift < 64 && (b & 0x40))
result -= (int64_t)1 << shift;
return result;
result |= ~(uint64_t)0 << shift;
return (int64_t)result;
}
}
return Fail();
@@ -147,6 +149,13 @@ enum {
// One .debug_line unit: header, then a bytecode program that walks a virtual machine whose output
// rows are (address, file, line). See the DWARF spec, "Line Number Information".
// Line 0 marks the end of a sequence, so real rows are at least 1.
static u32 ClampLine(int64_t line) {
return line > 0 ? (u32)std::min<int64_t>(line, UINT32_MAX) : 1;
}
static constexpr size_t MAX_FILES = 1 << 16;
static bool ParseUnit(Reader &r, size_t unitEnd, std::vector<LineEntry> *entries,
std::vector<std::string> *files, std::map<std::string, u32> *fileIndex, u32 moduleStart, u32 moduleSize, u32 addressDelta) {
const uint16_t version = r.U16();
@@ -158,6 +167,8 @@ static bool ParseUnit(Reader &r, size_t unitEnd, std::vector<LineEntry> *entries
}
const uint32_t headerLength = r.U32();
if ((u64)r.pos() + headerLength > unitEnd)
return false;
const size_t programStart = r.pos() + headerLength;
const uint8_t minInstLength = r.U8();
@@ -192,6 +203,11 @@ static bool ParseUnit(Reader &r, size_t unitEnd, std::vector<LineEntry> *entries
// Deduped across units so a header included by 500 files is stored once.
auto it = fileIndex->find(name);
if (it == fileIndex->end()) {
// Each one costs far more memory than its bytes in the file.
if (files->size() >= MAX_FILES) {
unitFiles.push_back(0);
continue;
}
it = fileIndex->insert({ name, (u32)files->size() }).first;
files->push_back(name);
}
@@ -204,8 +220,12 @@ static bool ParseUnit(Reader &r, size_t unitEnd, std::vector<LineEntry> *entries
u32 address = 0;
u32 file = 1;
int line = 1;
// 64 bits, so the file can't overflow it with line advances.
int64_t line = 1;
bool sawAddress = false;
// Every one-byte special opcode can emit a row. After deduplication there's at most one per
// address, so more than a couple per instruction is a file trying to exhaust memory.
const size_t maxEntries = (size_t)moduleSize / 2 + 65536;
auto emit = [&](u32 lineNo) {
// Rows before a DW_LNE_set_address belong to no real code, and anything that doesn't land
@@ -213,6 +233,8 @@ static bool ParseUnit(Reader &r, size_t unitEnd, std::vector<LineEntry> *entries
const u32 finalAddress = addressDelta + address;
if (!sawAddress || finalAddress < moduleStart || finalAddress - moduleStart >= moduleSize)
return;
if (entries->size() >= maxEntries)
return;
LineEntry e;
e.address = finalAddress;
e.line = lineNo;
@@ -227,10 +249,14 @@ static bool ParseUnit(Reader &r, size_t unitEnd, std::vector<LineEntry> *entries
const uint8_t adjusted = op - opcodeBase;
address += (adjusted / lineRange) * minInstLength;
line += lineBase + (adjusted % lineRange);
emit(line > 0 ? (u32)line : 1);
emit(ClampLine(line));
} else if (op == 0) {
// Extended opcode, length-prefixed so unknown ones can be skipped.
const uint64_t length = r.ULEB();
// The length covers at least the sub-opcode. Check it before adding: a huge one wraps
// back to this opcode, which then loops forever.
if (!r.ok() || length == 0 || r.pos() > unitEnd || length > unitEnd - r.pos())
return false;
const size_t next = r.pos() + (size_t)length;
const uint8_t sub = r.U8();
if (sub == DW_LNE_end_sequence) {
@@ -248,13 +274,13 @@ static bool ParseUnit(Reader &r, size_t unitEnd, std::vector<LineEntry> *entries
} else {
switch (op) {
case DW_LNS_copy:
emit(line > 0 ? (u32)line : 1);
emit(ClampLine(line));
break;
case DW_LNS_advance_pc:
address += (u32)r.ULEB() * minInstLength;
break;
case DW_LNS_advance_line:
line += (int)r.SLEB();
line = std::clamp<int64_t>(line + std::clamp<int64_t>(r.SLEB(), INT32_MIN, INT32_MAX), INT32_MIN, UINT32_MAX);
break;
case DW_LNS_set_file:
file = (u32)r.ULEB();
@@ -293,36 +319,46 @@ int LineInfoMap::AddModule(std::string_view elfData, u32 moduleStart, u32 module
if (data[0] != ELFMAG0 || data[1] != ELFMAG1 || data[2] != ELFMAG2 || data[3] != ELFMAG3)
return 0;
const Elf32_Ehdr *header = (const Elf32_Ehdr *)data;
// Copied out, since the offsets in the file needn't be aligned. Sums in 64 bits, so they can't
// wrap a 32-bit size_t.
Elf32_Ehdr headerCopy;
memcpy(&headerCopy, data, sizeof(headerCopy));
const Elf32_Ehdr *header = &headerCopy;
if (header->e_shoff == 0 || header->e_shnum == 0 || header->e_shentsize < sizeof(Elf32_Shdr))
return 0;
if ((size_t)header->e_shoff + (size_t)header->e_shnum * header->e_shentsize > size)
if ((u64)header->e_shoff + (u64)header->e_shnum * header->e_shentsize > size)
return 0;
if (header->e_shstrndx >= header->e_shnum)
return 0;
auto section = [&](int i) {
return (const Elf32_Shdr *)(data + header->e_shoff + (size_t)i * header->e_shentsize);
Elf32_Shdr s;
memcpy(&s, data + header->e_shoff + (size_t)i * header->e_shentsize, sizeof(s));
return s;
};
const Elf32_Shdr *shstr = section(header->e_shstrndx);
if ((size_t)shstr->sh_offset + shstr->sh_size > size)
const Elf32_Shdr shstr = section(header->e_shstrndx);
if ((u64)shstr.sh_offset + shstr.sh_size > size)
return 0;
const Elf32_Shdr *debugLine = nullptr;
static const char debugLineName[] = ".debug_line";
Elf32_Shdr debugLine{};
bool foundDebugLine = false;
for (int i = 0; i < header->e_shnum; i++) {
const Elf32_Shdr *s = section(i);
if (s->sh_name >= shstr->sh_size)
const Elf32_Shdr s = section(i);
// The name, with its terminator, has to fit in the string table.
if (s.sh_name >= shstr.sh_size || shstr.sh_size - s.sh_name < sizeof(debugLineName))
continue;
const char *name = (const char *)data + shstr->sh_offset + s->sh_name;
if (!strcmp(name, ".debug_line")) {
const char *name = (const char *)data + shstr.sh_offset + s.sh_name;
if (!memcmp(name, debugLineName, sizeof(debugLineName))) {
debugLine = s;
foundDebugLine = true;
break;
}
}
if (!debugLine || debugLine->sh_size == 0)
if (!foundDebugLine || debugLine.sh_size == 0)
return 0;
if ((size_t)debugLine->sh_offset + debugLine->sh_size > size)
if ((u64)debugLine.sh_offset + debugLine.sh_size > size)
return 0;
ModuleLines mod;
@@ -331,9 +367,9 @@ int LineInfoMap::AddModule(std::string_view elfData, u32 moduleStart, u32 module
mod.files.push_back("<unknown>");
std::map<std::string, u32> fileIndex;
Reader r(data + debugLine->sh_offset, debugLine->sh_size);
Reader r(data + debugLine.sh_offset, debugLine.sh_size);
int skippedUnits = 0;
while (r.ok() && !r.AtEnd(debugLine->sh_size)) {
while (r.ok() && !r.AtEnd(debugLine.sh_size)) {
const size_t unitStart = r.pos();
const uint32_t unitLength = r.U32();
if (!r.ok() || unitLength == 0)
@@ -342,9 +378,9 @@ int LineInfoMap::AddModule(std::string_view elfData, u32 moduleStart, u32 module
// produces, and misreading it would walk off into nonsense.
if (unitLength >= 0xfffffff0)
break;
const size_t unitEnd = unitStart + 4 + unitLength;
if (unitEnd > debugLine->sh_size)
if ((u64)unitStart + 4 + unitLength > debugLine.sh_size)
break;
const size_t unitEnd = unitStart + 4 + unitLength;
if (!ParseUnit(r, unitEnd, &mod.entries, &mod.files, &fileIndex, moduleStart, moduleSize, addressDelta))
skippedUnits++;
@@ -363,9 +399,16 @@ int LineInfoMap::AddModule(std::string_view elfData, u32 moduleStart, u32 module
std::stable_sort(mod.entries.begin(), mod.entries.end(), [](const LineEntry &a, const LineEntry &b) {
return a.address < b.address;
});
mod.entries.erase(std::unique(mod.entries.begin(), mod.entries.end(), [](const LineEntry &a, const LineEntry &b) {
return a.address == b.address;
}), mod.entries.end());
// (std::unique would keep the first.)
size_t out = 0;
for (size_t i = 0; i < mod.entries.size(); i++) {
if (out > 0 && mod.entries[out - 1].address == mod.entries[i].address) {
mod.entries[out - 1] = mod.entries[i];
} else {
mod.entries[out++] = mod.entries[i];
}
}
mod.entries.resize(out);
const int count = (int)mod.entries.size();
INFO_LOG(Log::Loader, "Line info: %d rows across %d files for module at %08x",
+4 -5
View File
@@ -108,11 +108,10 @@ bool SymbolMap::LoadSymbolMap(const Path &filename) {
if (p == NULL)
break;
// Chop any newlines off.
for (size_t i = strlen(line) - 1; i > 0; i--) {
if (line[i] == '\r' || line[i] == '\n') {
line[i] = '\0';
}
// Chop any newlines off. The line can be empty, if it starts with a NUL.
size_t len = strlen(line);
while (len > 0 && (line[len - 1] == '\r' || line[len - 1] == '\n')) {
line[--len] = '\0';
}
if (strlen(line) < 4 || sscanf(line, "%255s", temp) != 1)
+1 -1
View File
@@ -213,7 +213,7 @@ void PSPGamedataInstallDialog::WriteSfoFile() {
ParamSFOData sfoFile;
std::string sfopath = GetGameDataInstallFileName(&request, SFO_FILENAME);
std::vector<u8> sfoFileData;
if (pspFileSystem.ReadEntireFile(sfopath, sfoFileData) >= 0) {
if (pspFileSystem.ReadEntireFile(sfopath, sfoFileData, false, 1024 * 1024) >= 0) {
sfoFile.ReadSFO(sfoFileData);
}
+90 -45
View File
@@ -15,6 +15,9 @@
// Official git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
#include <algorithm>
#include <cstring>
#include "Common/StringUtils.h"
#include "Common/Data/Text/Demangle.h"
#include "Common/File/DirListing.h"
@@ -42,11 +45,15 @@ const char *ElfReader::GetSectionName(int section) const {
return nullptr;
}
const char *ptr = (const char *)GetSectionDataPtr(header->e_shstrndx);
if (ptr)
return ptr + nameOffset;
else
if (!ptr)
return nullptr;
// The name has to end before the file does.
const size_t maxLen = size_ - ((size_t)nameOffset + stringsOffset);
if (strnlen(ptr + nameOffset, maxLen) == maxLen) {
ERROR_LOG(Log::Loader, "ELF: Unterminated name in section %d", section);
return nullptr;
}
return ptr + nameOffset;
}
void addrToHiLo(u32 addr, u16 &hi, s16 &lo)
@@ -63,13 +70,51 @@ void addrToHiLo(u32 addr, u16 &hi, s16 &lo)
}
}
bool ElfReader::ConsumeRelocBudget(u64 bytes) {
relocBytesProcessed_ += bytes;
if (relocBytesProcessed_ > size_) {
ERROR_LOG_REPORT(Log::Loader, "ELF: More relocation data than the file holds, ignoring the rest");
return false;
}
return true;
}
bool ElfReader::LoadRelocations(const Elf32_Rel *rels, int numRelocs) {
if (numRelocs <= 0)
return true;
if (!ConsumeRelocBudget((u64)numRelocs * sizeof(Elf32_Rel)))
return false;
std::vector<u32> relocOps;
relocOps.resize(numRelocs);
DEBUG_LOG(Log::Loader, "Loading %i relocations...", numRelocs);
int numErrors = 0;
// A HI16 pairs with the next relocation that can serve as its LO16, skipping other HI16s.
// Whether one can doesn't depend on the HI16, so find them all in one backward pass, rather
// than scanning forward from every HI16 (quadratic, with a table of only HI16s).
auto isLoCandidate = [&](int t) {
int t_type = rels[t].r_info & 0xF;
// In MotorStorm: Arctic Edge (US), these are sometimes R_MIPS_16 (instead of LO16.)
// It appears the PSP takes any relocation that is not a HI16, but let's play it safe and
// skip the others, we've only seen R_MIPS_16.
if (t_type != R_MIPS_LO16 && t_type != R_MIPS_16)
return false;
// The candidate LO16 declares its own segment - use that rather than the HI16's,
// which is what the mismatch warning further down is there to detect.
int t_readwrite = (rels[t].r_info >> 8) & 0xff;
if (!SegmentIsLoaded(t_readwrite))
return false;
return Memory::IsValidAddress(rels[t].r_offset + segmentVAddr[t_readwrite]);
};
std::vector<int> nextLo(numRelocs + 1, -1);
for (int t = numRelocs - 1; t >= 0; t--) {
nextLo[t] = isLoCandidate(t) ? t : nextLo[t + 1];
}
// Relocations up to here have already been logged as skipped by an earlier HI16.
int loggedSkipsUpTo = 0;
{
for (int r = 0; r < numRelocs; r++) {
u32 info = rels[r].r_info;
@@ -97,8 +142,8 @@ bool ElfReader::LoadRelocations(const Elf32_Rel *rels, int numRelocs) {
addr += segmentVAddr[readwrite];
// It appears that misaligned relocations are allowed.
if (((addr & 3) && type != R_MIPS_32) || !Memory::IsValidAddress(addr)) {
// It appears that misaligned relocations are allowed. We read and write 4 bytes either way.
if (((addr & 3) && type != R_MIPS_32) || !Memory::IsValidRange(addr, 4)) {
if (numErrors < 10) {
WARN_LOG_REPORT(Log::Loader, "Suspicious address %08x, skipping reloc, type = %d", addr, type);
} else if (numErrors == 10) {
@@ -128,7 +173,7 @@ bool ElfReader::LoadRelocations(const Elf32_Rel *rels, int numRelocs) {
}
addr += segmentVAddr[readwrite];
if (((addr & 3) && type != R_MIPS_32) || !Memory::IsValidAddress(addr)) {
if (((addr & 3) && type != R_MIPS_32) || !Memory::IsValidRange(addr, 4)) {
continue;
}
@@ -164,31 +209,29 @@ bool ElfReader::LoadRelocations(const Elf32_Rel *rels, int numRelocs) {
u32 cur = (op & 0xFFFF) << 16;
u16 hi = 0;
bool found = false;
for (int t = r + 1; t < numRelocs; t++) {
int t_type = rels[t].r_info & 0xF;
if (t_type == R_MIPS_HI16)
const int t = nextLo[r + 1];
const int skipEnd = t < 0 ? numRelocs : t;
for (int s = std::max(r + 1, loggedSkipsUpTo); s < skipEnd; s++) {
int s_type = rels[s].r_info & 0xF;
int s_readwrite = (rels[s].r_info >> 8) & 0xff;
if (s_type == R_MIPS_HI16 || !SegmentIsLoaded(s_readwrite))
continue;
// The candidate LO16 declares its own segment - use that rather than the HI16's,
// which is what the mismatch warning further down is there to detect.
u32 skippedAddr = rels[s].r_offset + segmentVAddr[s_readwrite];
if (s_type != R_MIPS_LO16 && s_type != R_MIPS_16) {
// These exists in some popular games like Assassin's Creed: Bloodlines and GTA: VCS: (https://report.ppsspp.org/logs/kind/1187)
ERROR_LOG_REPORT(Log::Loader, "ELF relocation HI16/%d pair (instead of LO16) at %08x / %08x", s_type, addr, skippedAddr);
} else {
ERROR_LOG(Log::Loader, "Bad corrLoAddr %08x", skippedAddr);
}
}
loggedSkipsUpTo = std::max(loggedSkipsUpTo, skipEnd);
if (t >= 0) {
int t_type = rels[t].r_info & 0xF;
int t_readwrite = (rels[t].r_info >> 8) & 0xff;
if (!SegmentIsLoaded(t_readwrite))
continue;
u32 corrLoAddr = rels[t].r_offset + segmentVAddr[t_readwrite];
// In MotorStorm: Arctic Edge (US), these are sometimes R_MIPS_16 (instead of LO16.)
// It appears the PSP takes any relocation that is not a HI16.
if (t_type != R_MIPS_LO16) {
if (t_type != R_MIPS_16) {
// Let's play it safe for now and skip. We've only seen this type.
// These exists in some popular games like Assassin's Creed: Bloodlines and GTA: VCS: (https://report.ppsspp.org/logs/kind/1187)
ERROR_LOG_REPORT(Log::Loader, "ELF relocation HI16/%d pair (instead of LO16) at %08x / %08x", t_type, addr, corrLoAddr);
continue;
} else {
WARN_LOG_REPORT(Log::Loader, "ELF relocation HI16/%d(16) pair (instead of LO16) at %08x / %08x", t_type, addr, corrLoAddr);
}
if (t_type == R_MIPS_16) {
WARN_LOG_REPORT(Log::Loader, "ELF relocation HI16/%d(16) pair (instead of LO16) at %08x / %08x", t_type, addr, corrLoAddr);
}
// Should have matching index and segment info, according to llvm, which makes sense.
if ((rels[t].r_info >> 8) != (rels[r].r_info >> 8)) {
WARN_LOG_REPORT(Log::Loader, "ELF relocation HI16/LO16 with mismatching r_info lo=%08x, hi=%08x", rels[t].r_info, rels[r].r_info);
@@ -196,16 +239,11 @@ bool ElfReader::LoadRelocations(const Elf32_Rel *rels, int numRelocs) {
if (log) {
DEBUG_LOG(Log::Loader, "Corresponding lo found at %08x", corrLoAddr);
}
if (Memory::IsValidAddress(corrLoAddr)) {
s16 lo = (s16)relocOps[t];
cur += lo;
cur += relocateTo;
addrToHiLo(cur, hi, lo);
found = true;
break;
} else {
ERROR_LOG(Log::Loader, "Bad corrLoAddr %08x", corrLoAddr);
}
s16 lo = (s16)relocOps[t];
cur += lo;
cur += relocateTo;
addrToHiLo(cur, hi, lo);
found = true;
}
if (found) {
op = (op & 0xFFFF0000) | hi;
@@ -289,6 +327,8 @@ void ElfReader::LoadRelocations2(int rel_seg)
ERROR_LOG_REPORT(Log::Loader, "Rel2 segment extends past the end of the file");
return;
}
if (!ConsumeRelocBudget(ph->p_filesz))
return;
end = buf+ph->p_filesz;
// Everything below reads forward from buf, so check there's something there each time. All of
@@ -311,6 +351,12 @@ void ElfReader::LoadRelocations2(int rel_seg)
while((1<<seg_bits)<rel_seg)
seg_bits += 1;
// The three fields are packed into 16-bit commands, and the shifts below assume they fit.
if (flag_bits + seg_bits + type_bits > 16) {
ERROR_LOG_REPORT(Log::Loader, "Rel2: bad field sizes %d/%d/%d", flag_bits, seg_bits, type_bits);
return;
}
buf += 4;
// Both tables are prefixed by their own size, and are indexed by bitfields out of the command
@@ -522,13 +568,12 @@ void ElfReader::LoadRelocations2(int rel_seg)
int ElfReader::LoadInto(u32 loadAddress, bool fromTop) {
DEBUG_LOG(Log::Loader,"String section: %i", header->e_shstrndx);
if (size_ < sizeof(Elf32_Ehdr)) {
ERROR_LOG(Log::Loader, "Truncated ELF header, %d bytes", (int)size_);
// Probably not the right error code.
return SCE_KERNEL_ERROR_MEMBLOCK_ALLOC_FAILED;
}
DEBUG_LOG(Log::Loader,"String section: %i", header->e_shstrndx);
if (header->e_ident[0] != ELFMAG0 || header->e_ident[1] != ELFMAG1
|| header->e_ident[2] != ELFMAG2 || header->e_ident[3] != ELFMAG3)
@@ -546,14 +591,14 @@ int ElfReader::LoadInto(u32 loadAddress, bool fromTop) {
if (header->e_ident[EI_DATA] != ELFDATA2LSB)
return SCE_KERNEL_ERROR_MEMBLOCK_ALLOC_FAILED;
const size_t phdrEnd = header->e_phoff + sizeof(Elf32_Phdr) * GetNumSegments();
const size_t shdrEnd = header->e_shoff + sizeof(Elf32_Shdr) * GetNumSections();
const u64 phdrEnd = (u64)header->e_phoff + sizeof(Elf32_Phdr) * GetNumSegments();
const u64 shdrEnd = (u64)header->e_shoff + sizeof(Elf32_Shdr) * GetNumSections();
if (size_ < phdrEnd || size_ < shdrEnd) {
// Say which table runs off the end and by how much. "Truncated ELF" on its own sends you
// looking at the executable, when the usual cause is that we were handed fewer bytes than
// the file really has - a short read, or a size clamped somewhere upstream.
const char *which = size_ < phdrEnd ? "program header table" : "section header table";
const size_t needed = size_ < phdrEnd ? phdrEnd : shdrEnd;
const u64 needed = size_ < phdrEnd ? phdrEnd : shdrEnd;
loadError_ = StringFromFormat(
"Truncated ELF: %s ends at %d but only %d bytes are available (%d short), %d sections, %d segments",
which, (int)needed, (int)size_, (int)(needed - size_), GetNumSections(), GetNumSegments());
@@ -793,7 +838,7 @@ int ElfReader::LoadInto(u32 loadAddress, bool fromTop) {
int numRelocs = s->sh_size / sizeof(Elf32_Rel);
Elf32_Rel *rels = (Elf32_Rel *)GetSectionDataPtr(i);
if (GetSectionDataOffset(i) + sizeof(Elf32_Rel) * numRelocs > size_)
if ((u64)GetSectionDataOffset(i) + sizeof(Elf32_Rel) * (u64)numRelocs > size_)
rels = nullptr;
DEBUG_LOG(Log::Loader,"%s: Performing %i relocations on %s : offset = %08x", name, numRelocs, GetSectionName(sectionToModify), sections[i].sh_offset);
@@ -845,7 +890,7 @@ int ElfReader::LoadInto(u32 loadAddress, bool fromTop) {
int numRelocs = p->p_filesz / sizeof(Elf32_Rel);
Elf32_Rel *rels = (Elf32_Rel *)GetSegmentPtr(i);
if (p->p_offset + p->p_filesz > size_)
if ((u64)p->p_offset + p->p_filesz > size_)
rels = nullptr;
if (!rels || !LoadRelocations(rels, numRelocs)) {
ERROR_LOG(Log::Loader, "LoadInto: Relocs failed, trying anyway (2)");
+4
View File
@@ -202,6 +202,10 @@ private:
// array that callers are trusted to stay inside.
std::vector<u32> segmentVAddr;
size_t size_ = 0;
// Relocation data processed so far. Several relocation sections or segments can point at the
// same table, so this is capped at the file size to keep the total work linear.
u64 relocBytesProcessed_ = 0;
bool ConsumeRelocBudget(u64 bytes);
u32 firstSegAlign = 0;
std::string loadError_;
};
+14 -13
View File
@@ -146,7 +146,7 @@ bool ParamSFOData::ReadSFO(const u8 *paramsfo, size_t size) {
// The index table itself must fit entirely within the buffer before we
// can dereference entries; otherwise indexTables[i] reads out of bounds.
if (sizeof(Header) + (size_t)header->index_table_entries * sizeof(IndexTable) > size) {
if (sizeof(Header) + (u64)header->index_table_entries * sizeof(IndexTable) > size) {
return false;
}
@@ -172,15 +172,17 @@ bool ParamSFOData::ReadSFO(const u8 *paramsfo, size_t size) {
for (u32 i = 0; i < header->index_table_entries; i++)
{
size_t key_offset = header->key_table_start + indexTables[i].key_table_offset;
if (key_offset >= size) {
const u64 key_offset64 = (u64)header->key_table_start + indexTables[i].key_table_offset;
if (key_offset64 >= size) {
return false;
}
const size_t key_offset = (size_t)key_offset64;
size_t data_offset = header->data_table_start + indexTables[i].data_table_offset;
if (data_offset >= size) {
const u64 data_offset64 = (u64)header->data_table_start + indexTables[i].data_table_offset;
if (data_offset64 >= size) {
return false;
}
const size_t data_offset = (size_t)data_offset64;
std::string key = readStringCapped(key_offset, 0);
if (key.empty())
@@ -200,7 +202,7 @@ bool ParamSFOData::ReadSFO(const u8 *paramsfo, size_t size) {
case 0x0004:
// Special format UTF-8
{
if (data_offset + indexTables[i].param_len > size)
if ((u64)data_offset + indexTables[i].param_len > size)
continue;
const u8 *utfdata = (const u8 *)(paramsfo + data_offset);
VERBOSE_LOG(Log::Loader, "%s %s", key.c_str(), utfdata);
@@ -242,7 +244,7 @@ int ParamSFOData::GetDataOffset(const u8 *paramsfo, size_t size, const char *dat
WARN_LOG(Log::Loader, "Unexpected SFO header version: %08x", header->version);
// Both the index table and the key/data tables must fit in the buffer.
if (sizeof(Header) + (size_t)header->index_table_entries * sizeof(IndexTable) > size)
if (sizeof(Header) + (u64)header->index_table_entries * sizeof(IndexTable) > size)
return -1;
if (header->key_table_start > size || header->data_table_start > size)
return -1;
@@ -254,19 +256,18 @@ int ParamSFOData::GetDataOffset(const u8 *paramsfo, size_t size, const char *dat
for (u32 i = 0; i < header->index_table_entries; i++)
{
// In size_t throughout - these are u32s from the file, and mixing them with int meant the
// bounds check below was done in whatever type the promotion landed on. ReadSFO does the
// same arithmetic this way.
size_t key_offset = header->key_table_start + indexTables[i].key_table_offset;
// In u64 - these are u32s from the file, and the sums can wrap a 32-bit size_t.
// ReadSFO does the same arithmetic this way.
const u64 key_offset = (u64)header->key_table_start + indexTables[i].key_table_offset;
if (key_offset >= size)
continue;
size_t data_offset = data_start + indexTables[i].data_table_offset;
const u64 data_offset = (u64)data_start + indexTables[i].data_table_offset;
if (data_offset >= size)
continue;
const char *key = (const char *)(key_start + indexTables[i].key_table_offset);
// Ensure the key string is NUL-terminated within the buffer before strcmp.
if (strnlen(key, size - key_offset) == size - key_offset)
if (strnlen(key, (size_t)(size - key_offset)) == size - key_offset)
continue;
if (!strcmp(key, dataName))
{
+18 -7
View File
@@ -972,7 +972,7 @@ static int pspDecryptType0(KirkState *kirk, const u8 *inbuf, u8 *outbuf, u32 siz
memcpy(reinterpret_cast<u8*>(header)+sizeof(KIRK_CMD1_HEADER), type0.prxHeader, sizeof(type0.prxHeader));
decryptKirkHeaderType0(reinterpret_cast<u8*>(header), type0.kirkBlock, xorbuf, pti->code);
if (!KirkOutputFits(header, size))
if (!KirkOutputFits(header, size - offset))
{
return -5;
}
@@ -1031,7 +1031,7 @@ static int pspDecryptType1(KirkState *kirk, const u8 *inbuf, u8 *outbuf, u32 siz
memcpy(reinterpret_cast<u8*>(header)+sizeof(KIRK_CMD1_HEADER), type1.prxHeader, sizeof(type1.prxHeader));
decryptKirkHeaderType0(reinterpret_cast<u8*>(header), type1.kirkBlock, xorbuf, pti->code);
if (!KirkOutputFits(header, size))
if (!KirkOutputFits(header, size - offset))
{
return -5;
}
@@ -1099,7 +1099,7 @@ static int pspDecryptType2(KirkState *kirk, const u8 *inbuf, u8 *outbuf, u32 siz
decryptKirkHeader(reinterpret_cast<u8*>(header), type2.kirkHeader, xorbuf.cbegin()+0x10, pti->code);
header->mode = 1;
if (!KirkOutputFits(header, size))
if (!KirkOutputFits(header, size - offset))
{
return -5;
}
@@ -1150,7 +1150,7 @@ static int pspFinishType5(KirkState *kirk, const u8 *inbuf, u8 *outbuf, u32 size
decryptKirkHeader(reinterpret_cast<u8*>(header), type5.kirkHeader, xorbuf.cbegin()+0x10, code);
header->mode = 1;
if (!KirkOutputFits(header, size))
if (!KirkOutputFits(header, size - offset))
{
return -5;
}
@@ -1275,7 +1275,7 @@ static int pspDecryptType6(KirkState *kirk, const u8 *inbuf, u8 *outbuf, u32 siz
header->mode = 1;
header->ecdsa_hash = 1;
if (!KirkOutputFits(header, size))
if (!KirkOutputFits(header, size - offset))
{
return -5;
}
@@ -1352,7 +1352,7 @@ static int pspDecryptType9(KirkState *kirk, const u8 *inbuf, u8 *outbuf, u32 siz
// branch only writes the mode word and zeroes the rest of that region.
header->ecdsa_hash = 0;
if (!KirkOutputFits(header, size))
if (!KirkOutputFits(header, size - offset))
{
return -5;
}
@@ -1365,7 +1365,7 @@ static int pspDecryptType9(KirkState *kirk, const u8 *inbuf, u8 *outbuf, u32 siz
return decryptSize;
}
int pspDecryptPRX(const u8 *inbuf, u8 *outbuf, u32 size, const u8 *seed, const u32 *fuseId)
static int pspDecryptPRXAnyType(const u8 *inbuf, u8 *outbuf, u32 size, const u8 *seed, const u32 *fuseId)
{
// Every type below reads the tag at 0xD0, the size at 0xB0 and key data as far as 0x150, and
// writes a KIRK header into outbuf at a fixed offset derived from sizeof(PSP_Header). Without
@@ -1423,6 +1423,17 @@ int pspDecryptPRX(const u8 *inbuf, u8 *outbuf, u32 size, const u8 *seed, const u
return pspDecryptType9(&kirk, inbuf, outbuf, size);
}
int pspDecryptPRX(const u8 *inbuf, u8 *outbuf, u32 size, const u8 *seed, const u32 *fuseId)
{
const int res = pspDecryptPRXAnyType(inbuf, outbuf, size, seed, fuseId);
// The decrypted size comes from the header, and callers size their reads of outbuf by it.
if (res > 0 && (u32)res > size) {
ERROR_LOG(Log::Loader, "PRX claims a decrypted size of %d, larger than its %d bytes", res, (int)size);
return -1;
}
return res;
}
bool pspUnwrapDNASPGD(std::vector<u8> &data, const u8 *secureId, const u32 *fuseId)
{
if (data.size() < 0x90 || memcmp(data.data(), "\0PGD", 4) != 0) {
+22 -1
View File
@@ -87,11 +87,15 @@ size_t DiskCachingFileLoader::ReadAt(s64 absolutePos, size_t bytes, void *data,
Prepare();
size_t readSize;
if (absolutePos >= filesize_) {
if (absolutePos < 0 || absolutePos >= filesize_) {
bytes = 0;
} else if (absolutePos + (s64)bytes >= filesize_) {
bytes = (size_t)(filesize_ - absolutePos);
}
// The cache computes the last block as pos + bytes - 1.
if (bytes == 0) {
return 0;
}
if (cache_ && cache_->IsValid() && (flags & Flags::HINT_UNCACHED) == 0) {
readSize = cache_->ReadFromCache(absolutePos, bytes, data);
@@ -357,6 +361,7 @@ bool DiskCachingFileLoaderCache::MakeCacheSpaceFor(size_t blocks) {
while (cacheSize_ > goal) {
u16 minGeneration = generation_;
bool evicted = false;
// We increment the iterator inside because we delete things inside.
for (size_t i = 0; i < blockIndexLookup_.size(); ++i) {
@@ -377,6 +382,7 @@ bool DiskCachingFileLoaderCache::MakeCacheSpaceFor(size_t blocks) {
info.generation = 0;
info.hits = 0;
--cacheSize_;
evicted = true;
// TODO: Doing this in chunks might be a lot better.
WriteIndexData(blockIndexLookup_[i], info);
@@ -389,6 +395,12 @@ bool DiskCachingFileLoaderCache::MakeCacheSpaceFor(size_t blocks) {
}
}
// If we found nothing to evict and the next pass would look for the same generation,
// it can't make progress either.
if (!evicted && minGeneration == oldestGeneration_) {
ERROR_LOG(Log::Loader, "Disk cache: Nothing left to evict, %d blocks counted", (int)cacheSize_);
break;
}
// If we didn't find any, update to the lowest we did find.
oldestGeneration_ = minGeneration;
}
@@ -548,6 +560,9 @@ bool DiskCachingFileLoaderCache::LoadCacheFile(const Path &path) {
} else if (header.maxBlocks < MAX_BLOCKS_LOWER_BOUND || header.maxBlocks > MAX_BLOCKS_UPPER_BOUND) {
// This means it's not in our safety bounds, reject.
valid = false;
} else if (header.blockSize != DEFAULT_BLOCK_SIZE) {
// We only ever create these, and everything below divides by and allocates the block size.
valid = false;
}
// If it's valid, retain the file pointer.
@@ -600,6 +615,12 @@ void DiskCachingFileLoaderCache::LoadCacheIndex() {
if (index_[i].block == INVALID_BLOCK) {
continue;
}
// Two entries can't share a data block. Keeping both would serve one of them the wrong
// data, and count a block that eviction can't find (which then never finishes).
if (blockIndexLookup_[index_[i].block] != INVALID_INDEX) {
index_[i].block = INVALID_BLOCK;
continue;
}
if (index_[i].generation < oldestGeneration_) {
oldestGeneration_ = index_[i].generation;
+5
View File
@@ -89,6 +89,11 @@ void HTTPFileLoader::Prepare() {
}
if (size_pos != header.npos) {
filesize_ = atoll(&header[size_pos]);
// The caches size their indexes from this. Nothing we'd load is this big.
if (filesize_ < 0 || filesize_ > (64LL << 30)) {
ERROR_LOG(Log::Loader, "HTTP: Implausible Content-Length: %s", header.c_str());
filesize_ = 0;
}
}
}
if (startsWithNoCase(header, "Accept-Ranges:")) {
+5 -2
View File
@@ -428,13 +428,13 @@ bool PS1EbootFileLoader::ParseMds(const Path &mdsPath, const std::string &data)
const u8 *d = (const u8 *)data.data();
const size_t size = data.size();
auto read32 = [&](size_t offset) -> u32 {
return offset + 4 <= size ? (d[offset] | (d[offset + 1] << 8) | (d[offset + 2] << 16) | ((u32)d[offset + 3] << 24)) : 0;
return offset <= size && size - offset >= 4 ? (d[offset] | (d[offset + 1] << 8) | (d[offset + 2] << 16) | ((u32)d[offset + 3] << 24)) : 0;
};
if (size < 0x58 || memcmp(d, "MEDIA DESCRIPTOR", 16) != 0) {
return false;
}
const u32 session = read32(0x50);
if (session + 0x18 > size) {
if ((size_t)session + 0x18 > size) {
return false;
}
const u32 numBlocks = d[session + 0x0A];
@@ -466,6 +466,9 @@ bool PS1EbootFileLoader::ParseMds(const Path &mdsPath, const std::string &data)
if (file < 0) {
// "*.mdf" is the .mds's own name.
const u32 nameBlock = read32(b + 0x34);
if ((size_t)nameBlock + 5 > size) {
return false;
}
const u32 nameOffset = read32(nameBlock);
std::string name;
for (u32 p = nameOffset; p < size && d[p] != 0; p += d[nameBlock + 4] ? 2 : 1) {
@@ -88,6 +88,11 @@ size_t RamCachingFileLoader::ReadAt(s64 absolutePos, size_t bytes, void *data, F
void RamCachingFileLoader::InitCache() {
std::lock_guard<std::mutex> guard(blocksMutex_);
// The whole file has to fit in memory (and a size_t, which may be 32 bits).
if (filesize_ <= 0 || (u64)filesize_ > (u64)(SIZE_MAX - BLOCK_SIZE) || (u64)filesize_ > (8ULL << 30)) {
ERROR_LOG(Log::IO, "Can't cache a file of %lld bytes in RAM, will fall back to regular reads.", (long long)filesize_);
return;
}
u32 blockCount = (u32)((filesize_ + BLOCK_SIZE - 1) >> BLOCK_SHIFT);
// Overallocate for the last block.
cache_ = (u8 *)malloc((size_t)blockCount << BLOCK_SHIFT);
+6
View File
@@ -74,6 +74,12 @@ bool ZipFileLoader::Initialize(int fileIndex) {
}
_dbg_assert_(zstat.index == fileIndex);
// The whole file is decompressed into memory. No disc image is anywhere near 4GB, and the
// size has to fit in a (possibly 32-bit) size_t too.
if (!(zstat.valid & ZIP_STAT_SIZE) || zstat.size > (4ULL << 30) || zstat.size > (u64)SIZE_MAX) {
ERROR_LOG(Log::IO, "File index %d in ZIP has an implausible size (%llu)", fileIndex, (unsigned long long)zstat.size);
return false;
}
dataFileSize_ = zstat.size;
dataFile_ = zip_fopen_index(zipArchive_, zstat.index, ZIP_FL_UNCHANGED);
if (!dataFile_) {
+4
View File
@@ -92,6 +92,10 @@ size_t BlobFileSystem::SeekFile(u32 handle, s32 position, FileMove type) {
entry->second = fileLoader_->FileSize() + position;
break;
}
// Reads take the position as a file offset.
if (entry->second < 0) {
entry->second = 0;
}
return (size_t)entry->second;
}
return 0;
+51 -10
View File
@@ -99,7 +99,7 @@ static bool ParseUDFFileEntryExtent(FileLoader *fileLoader, u32 sector, UDFShort
u32 extAttrLen = ReadLE32(block.data() + 0xA8);
u32 allocDescLen = ReadLE32(block.data() + 0xAC);
u32 allocDescOffset = 0xB0 + extAttrLen;
const u64 allocDescOffset = 0xB0 + (u64)extAttrLen;
if (allocDescLen < 8 || allocDescOffset + 8 > block.size())
return false;
@@ -110,11 +110,14 @@ static bool ParseUDFRootDirectory(FileLoader *fileLoader, u32 sector, u32 partit
UDFShortAd extent{};
if (!ParseUDFFileEntryExtent(fileLoader, sector, &extent))
return false;
if (extent.length == 0)
// A root directory is a few KB, so this cap is very generous.
if (extent.length == 0 || extent.length > 1024 * 1024)
return false;
const u64 offset = ((u64)partitionStart + extent.position) * 2048;
if (offset + extent.length > fileLoader->FileSize())
return false;
dirData->resize(extent.length);
const u64 offset = (u64)(partitionStart + extent.position) * 2048;
return fileLoader->ReadAt(offset, 1, dirData->size(), dirData->data()) == dirData->size();
}
@@ -191,7 +194,8 @@ static bool FindUDFLayerFileEntrySectors(FileLoader *fileLoader, u32 rootSector,
if (entryLen == 0 || offset + entryLen > dirData.size())
break;
if (tag == 0x0101 && crcLen >= 20) {
// The fixed part of a File Identifier Descriptor is 38 bytes.
if (tag == 0x0101 && crcLen >= 22) {
u8 fileIdLen = dirData[offset + 19];
u16 implUseLen = ReadLE16(&dirData[offset + 36]);
size_t nameOffset = offset + 38 + implUseLen;
@@ -567,6 +571,10 @@ CISOFileBlockDevice::CISOFileBlockDevice(FileLoader *fileLoader)
return;
}
const u64 totalSize = hdr.total_bytes;
if (totalSize > MAX_CSO_IMAGE_SIZE) {
errorString_ = "Invalid CSO header (image is too large)";
return;
}
// Compute the counts without overflowing the 64-bit total size, then
// validate them before truncating to the 32-bit fields used by the CSO
// block reader. In particular, numFrames + 1 must remain representable.
@@ -739,6 +747,9 @@ bool CISOFileBlockDevice::ReadBlock(int blockNumber, u8 *outPtr, bool uncached)
}
bool CISOFileBlockDevice::ReadBlocks(u32 minBlock, int count, u8 *outPtr) {
if (count <= 0) {
return true;
}
if (count == 1) {
return ReadBlock(minBlock, outPtr);
}
@@ -801,7 +812,18 @@ bool CISOFileBlockDevice::ReadBlocks(u32 minBlock, int count, u8 *outPtr) {
}
if (plain) {
memcpy(outPtr, rawBuffer + frameBlockOffset * GetBlockSize(), frameBlocks * GetBlockSize());
// The frame's data may be shorter than a full frame (or the read came up short),
// so only copy what's actually in the buffer.
const u64 available = std::min(frameReadEnd, readBufferEnd) - frameReadPos;
const u64 copyStart = (u64)frameBlockOffset * GetBlockSize();
const u64 copyBytes = (u64)frameBlocks * GetBlockSize();
const u64 copied = available > copyStart ? std::min(available - copyStart, copyBytes) : 0;
if (copied) {
memcpy(outPtr, rawBuffer + copyStart, (size_t)copied);
}
if (copied < copyBytes) {
memset(outPtr + copied, 0, (size_t)(copyBytes - copied));
}
} else {
z.avail_in = frameReadSize;
z.next_out = frameBlocks == blocksPerFrame ? outPtr : zlibBuffer;
@@ -980,14 +1002,15 @@ bool NPDRMDemoBlockDevice::ReadBlock(int blockNumber, u8 *outPtr, bool uncached)
}
int lba = blockNumber - currentBlock_;
if (lba >= 0 && lba < blockLBAs_){
if (currentBlock_ >= 0 && lba >= 0 && lba < blockLBAs_){
memcpy(outPtr, blockBuf_ + lba*2048, 2048);
return true;
}
int block = blockNumber / blockLBAs_;
lba = blockNumber % blockLBAs_;
currentBlock_ = block * blockLBAs_;
// Only marked valid once blockBuf_ has been filled successfully.
currentBlock_ = -1;
// blockNumber comes from the caller (ultimately from guest-controlled reads, e.g.
// via /sce_lbn.../_size... on a raw sector open) and isn't otherwise guaranteed to be
@@ -997,6 +1020,7 @@ bool NPDRMDemoBlockDevice::ReadBlock(int blockNumber, u8 *outPtr, bool uncached)
}
if (table_[block].unk_1c != 0) {
memset(outPtr, 0, 2048);
if((u32)block == (numBlocks_ - 1))
return true; // demos make by fake_np
else
@@ -1019,6 +1043,7 @@ bool NPDRMDemoBlockDevice::ReadBlock(int blockNumber, u8 *outPtr, bool uncached)
size_t readSize = fileLoader_->ReadAt(psarOffset+table_[block].offset, 1, table_[block].size, readBuf, flags);
if (readSize != (size_t)table_[block].size){
memset(outPtr, 0, 2048);
if((u32)block==(numBlocks_-1))
return true;
else
@@ -1047,6 +1072,7 @@ bool NPDRMDemoBlockDevice::ReadBlock(int blockNumber, u8 *outPtr, bool uncached)
}
}
currentBlock_ = block * blockLBAs_;
memcpy(outPtr, blockBuf_+lba*2048, 2048);
return true;
}
@@ -1091,10 +1117,13 @@ chd_file *OpenCHDThroughLoader(FileLoader *fileLoader, std::string *error) {
coreFile->core.fread = [](void *out_data, size_t size, size_t count, core_file *file) {
ExtendedCoreFile *coreFile = (ExtendedCoreFile *)file;
FileLoader *loader = (FileLoader *)file->argp;
if (size == 0) {
return (size_t)0;
}
uint64_t totalSize = size * count;
loader->ReadAt(coreFile->seekPos, totalSize, out_data);
coreFile->seekPos += totalSize;
return size * count;
size_t bytesRead = loader->ReadAt(coreFile->seekPos, totalSize, out_data);
coreFile->seekPos += bytesRead;
return bytesRead / size;
};
coreFile->core.fclose = [](core_file *file) {
ExtendedCoreFile *coreFile = (ExtendedCoreFile *)file;
@@ -1173,6 +1202,15 @@ CHDFileBlockDevice::CHDFileBlockDevice(FileLoader *fileLoader)
impl_->chd = file;
impl_->header = chd_get_header(impl_->chd);
// We copy 2048 bytes out of each unit, and need at least one unit per hunk
// (otherwise the block math below divides by zero or reads outside the hunk).
if (impl_->header->unitbytes < 2048 || impl_->header->hunkbytes < impl_->header->unitbytes) {
errorString_ = StringFromFormat("CHD has unsupported unit size %d (hunk size %d)", impl_->header->unitbytes, impl_->header->hunkbytes);
chd_close(file);
impl_->chd = nullptr;
return;
}
readBuffer = new u8[impl_->header->hunkbytes];
currentHunk = -1;
blocksPerHunk = impl_->header->hunkbytes / impl_->header->unitbytes;
@@ -1205,6 +1243,9 @@ bool CHDFileBlockDevice::ReadBlock(int blockNumber, u8 *outPtr, bool uncached) {
if (err != CHDERR_NONE) {
ERROR_LOG(Log::Loader, "CHD read failed: %d %d %s", blockNumber, hunk, chd_error_string(err));
NotifyReadError();
currentHunk = -1;
memset(outPtr, 0, GetBlockSize());
return false;
}
currentHunk = hunk;
}
+4
View File
@@ -65,6 +65,10 @@ protected:
std::string errorString_;
};
// A dual-layer UMD holds 1.8 GB, so this is generous. A CSO header can claim any size,
// independent of how big the file is.
constexpr u64 MAX_CSO_IMAGE_SIZE = 8ULL * 1024 * 1024 * 1024;
class CISOFileBlockDevice : public BlockDevice {
public:
CISOFileBlockDevice(FileLoader *fileLoader);
+7
View File
@@ -187,6 +187,13 @@ bool Compress(const Path &input, const Path &output, Preset preset, const Progre
return false;
}
// No PSP disc is anywhere near this. The size can come from a CSO header, which can claim
// far more than the file holds, and the index below is sized from it.
if (src.size > MAX_CSO_IMAGE_SIZE) {
*error = "Input image is too large";
return false;
}
const PresetSettings settings = SettingsFor(preset);
const uint32_t blockSize = settings.blockSize;
const uint32_t sectorsPerBlock = blockSize / SECTOR_SIZE;
+17 -3
View File
@@ -233,7 +233,17 @@ std::string ISOFileSystem::TreeEntry::BuildPath() {
}
void ISOFileSystem::ReadDirectory(TreeEntry *root) const {
for (u32 secnum = root->startsector, endsector = root->startsector + (root->dirsize + 2047) / 2048; secnum < endsector; ++secnum) {
// Real directories are a few KB. The size comes from the image, so cap it to keep a corrupt
// one from making us parse (and allocate entries for) gigabytes of sectors.
const u32 MAX_DIRECTORY_SIZE = 4 * 1024 * 1024;
u32 dirsize = root->dirsize;
if (dirsize > MAX_DIRECTORY_SIZE) {
blockDevice->NotifyReadError();
ERROR_LOG(Log::FileSystem, "Directory '%s' claims an implausible size (%u), truncating", root->name.c_str(), dirsize);
dirsize = MAX_DIRECTORY_SIZE;
}
const u32 endsector = (u32)std::min<u64>((u64)root->startsector + (dirsize + 2047) / 2048, blockDevice->GetNumBlocks());
for (u32 secnum = root->startsector; secnum < endsector; ++secnum) {
u8 theSector[2048];
if (!blockDevice->ReadBlock(secnum, theSector)) {
blockDevice->NotifyReadError();
@@ -252,9 +262,10 @@ void ISOFileSystem::ReadDirectory(TreeEntry *root) const {
break;
const int IDENTIFIER_OFFSET = 33;
if (offset + IDENTIFIER_OFFSET + dir.identifierLength > 2048) {
if (offset + IDENTIFIER_OFFSET > 2048 || offset + IDENTIFIER_OFFSET + dir.identifierLength > 2048) {
blockDevice->NotifyReadError();
ERROR_LOG(Log::FileSystem, "Directory entry crosses sectors, corrupt iso?");
root->valid = true; // Prevents re-reading (and duplicating the children so far)
return;
}
// dir.size (the record length actually consumed) must cover at least its
@@ -265,6 +276,7 @@ void ISOFileSystem::ReadDirectory(TreeEntry *root) const {
if (dir.size < IDENTIFIER_OFFSET + dir.identifierLength) {
blockDevice->NotifyReadError();
ERROR_LOG(Log::FileSystem, "Directory entry size too small, corrupt iso?");
root->valid = true;
return;
}
@@ -522,7 +534,9 @@ int ISOFileSystem::Ioctl(u32 handle, u32 cmd, u32 indataPtr, u32 inlen, u32 outd
u8 *out = Memory::GetPointerWriteRangeOrException(outdataPtr, size);
int blocks = size / blockDevice->GetBlockSize();
blockDevice->ReadBlocks(block, blocks, out);
if (blocks > 0) {
blockDevice->ReadBlocks(block, blocks, out);
}
size -= blocks * blockDevice->GetBlockSize();
out += blocks * blockDevice->GetBlockSize();
+7 -2
View File
@@ -616,7 +616,7 @@ size_t MetaFileSystem::SeekFile(u32 handle, s32 position, FileMove type)
return 0;
}
int MetaFileSystem::ReadEntireFile(const std::string &filename, std::vector<u8> &data, bool quiet) {
int MetaFileSystem::ReadEntireFile(const std::string &filename, std::vector<u8> &data, bool quiet, size_t maxSize) {
FileAccess access = FILEACCESS_READ;
if (quiet) {
access = (FileAccess)(access | FILEACCESS_PPSSPP_QUIET);
@@ -626,8 +626,13 @@ int MetaFileSystem::ReadEntireFile(const std::string &filename, std::vector<u8>
return handle;
SeekFile(handle, 0, FILEMOVE_END);
size_t dataSize = GetSeekPos(handle);
const s64 fileSize = GetSeekPos(handle);
SeekFile(handle, 0, FILEMOVE_BEGIN);
if (fileSize < 0 || (u64)fileSize > maxSize) {
CloseFile(handle);
return SCE_KERNEL_ERROR_ERROR;
}
const size_t dataSize = (size_t)fileSize;
data.resize(dataSize);
size_t result = ReadFile(handle, data.data(), dataSize);
+2 -1
View File
@@ -151,7 +151,8 @@ public:
u64 FreeDiskSpace(const std::string &path) override;
// Convenience helper - returns < 0 on failure.
int ReadEntireFile(const std::string &filename, std::vector<u8> &data, bool quiet = false);
// Fails (without allocating) if the file is larger than maxSize.
int ReadEntireFile(const std::string &filename, std::vector<u8> &data, bool quiet = false, size_t maxSize = (size_t)-1);
void SetStartingDirectory(std::string_view dir) {
std::lock_guard<std::recursive_mutex> guard(lock);
+7 -3
View File
@@ -177,7 +177,9 @@ static int rc_bittree(LZRC_DECODE *rc, u8 *probs, int limit)
*/
static int rc_number(LZRC_DECODE *rc, u8 *prob, int n)
{
int i, number = 1;
// Unsigned, since corrupt data can ask for more direct bits than fit (it then wraps).
int i;
u32 number = 1;
if(n>3){
number = (number<<1)+rc_bit(rc, prob+3);
@@ -210,7 +212,7 @@ static int rc_number(LZRC_DECODE *rc, u8 *prob, int n)
}
}
return number;
return (int)number;
}
@@ -305,13 +307,15 @@ int lzrc_decompress(void *out, int out_len, void *in, int in_len)
}
/* copy match bytes */
if(match_dist>rc.out_ptr || match_dist<0){
if(match_dist>rc.out_ptr || match_dist<=0){
printf("match_dist out of range! %08x\n", match_dist);
return -1;
}
match_src = rc.output+rc.out_ptr-match_dist;
for(i=0; i<match_len+1; i++){
rc_putbyte(&rc, *match_src++);
if (rc.error)
return -1;
}
rc_state = 6+((rc.out_ptr+1)&1);
}
+12 -2
View File
@@ -1048,8 +1048,13 @@ static u32 npdrmRead(FileNode *f, u8 *data, int size) {
block = pgd->file_offset/pgd->block_size;
offset = pgd->file_offset%pgd->block_size;
if (size > (int)pgd->data_size)
size = (int)pgd->data_size;
// Unsigned: data_size comes from the (forgeable) header, and a value over 2^31 turned the
// clamp into a huge read. Only what's left of the file can be read, too.
const u32 available = pgd->file_offset < pgd->data_size ? pgd->data_size - pgd->file_offset : 0;
if (size < 0)
size = 0;
if ((u32)size > available)
size = (int)available;
remain_size = size;
KirkState *kirk = __ChnnlsvKirkState();
@@ -3093,6 +3098,11 @@ int __IoIoctl(u32 id, u32 cmd, u32 indataPtr, u32 inlen, u32 outdataPtr, u32 out
pspFileSystem.SeekFile(f->handle, (s32)f->pgd_offset, FILEMOVE_BEGIN);
pspFileSystem.ReadFile(f->handle, pgd_header, 0x90);
KirkState *kirk = __ChnnlsvKirkState();
// Calling this again would otherwise leak the previous one.
if (f->pgdInfo) {
pgd_close(f->pgdInfo);
f->pgdInfo = nullptr;
}
f->pgdInfo = pgd_open(kirk, pgd_header, 2, key_ptr);
if (!f->pgdInfo) {
f->npdrm = false;
+65 -28
View File
@@ -1256,14 +1256,14 @@ static bool KernelImportModuleFuncs(PSPModule *module, u32 *firstImportStubAddr,
return true;
}
static int gzipDecompress(u8 *OutBuffer, int OutBufferLength, u8 *InBuffer) {
static int gzipDecompress(u8 *OutBuffer, int OutBufferLength, const u8 *InBuffer, int InBufferLength) {
int err;
z_stream stream;
u8 *outBufferPtr;
outBufferPtr = OutBuffer;
stream.next_in = InBuffer;
stream.avail_in = (uInt)OutBufferLength;
stream.next_in = (Bytef *)InBuffer;
stream.avail_in = (uInt)InBufferLength;
stream.next_out = outBufferPtr;
stream.avail_out = (uInt)OutBufferLength;
stream.zalloc = (alloc_func)0;
@@ -1629,7 +1629,8 @@ static PSPModule *__KernelLoadELFFromPtr(const u8 *ptr, size_t elfSize, u32 load
if (Read32(ptr) == SCE_MAGIC) { // "~SCE"
const u32 headerSize = *(const u32_le*)(ptr + 4);
if (headerSize < elfSize) {
// Leave at least the magic read below.
if (headerSize < elfSize && elfSize - headerSize >= 4) {
// Parse and print the lib info
parsePrxLibInfo(ptr, headerSize);
@@ -1646,11 +1647,13 @@ static PSPModule *__KernelLoadELFFromPtr(const u8 *ptr, size_t elfSize, u32 load
DEBUG_LOG(Log::sceModule, "Decrypting ~PSP file");
head = (const PSP_Header *)ptr;
devkitVersion = head->devkitversion;
// Not necessarily terminated.
const std::string headModName(head->modname, strnlen(head->modname, sizeof(head->modname)));
bool wasDisabled;
if (ShouldHLEModuleForLoad(head->modname, &wasDisabled)) {
if (ShouldHLEModuleForLoad(headModName, &wasDisabled)) {
int ver = (head->module_ver_hi << 8) | head->module_ver_lo;
INFO_LOG(Log::sceModule, "Loading module %s with version %04x, devkit %08x, crc %x", head->modname, ver, head->devkitversion, module->crc);
INFO_LOG(Log::sceModule, "Loading module %s with version %04x, devkit %08x, crc %x", headModName.c_str(), ver, head->devkitversion, module->crc);
// TODO: Don't conflate these!
reportedModule = true;
@@ -1658,7 +1661,7 @@ static PSPModule *__KernelLoadELFFromPtr(const u8 *ptr, size_t elfSize, u32 load
}
if (wasDisabled) {
g_OSD.Show(OSDType::MESSAGE_WARNING, StringFromFormat("HLE for '%s' has been manually disabled", head->modname));
g_OSD.Show(OSDType::MESSAGE_WARNING, StringFromFormat("HLE for '%s' has been manually disabled", headModName.c_str()));
}
const u8 *in = ptr;
const bool isCompressed = (head->comp_attribute & 1) != 0;
@@ -1673,7 +1676,17 @@ static PSPModule *__KernelLoadELFFromPtr(const u8 *ptr, size_t elfSize, u32 load
return nullptr;
}
const auto maxElfSize = std::max(head->elf_size, head->psp_size);
newptr = new u8[maxElfSize];
// elf_size isn't bounded by anything else. Nothing this big fits in PSP RAM.
if (maxElfSize > 64 * 1024 * 1024) {
*error_string = StringFromFormat("ELF/PRX corrupt, unreasonable size: %u", (u32)maxElfSize);
module->Cleanup();
kernelObjects.Destroy<PSPModule>(module->GetUID());
error = SCE_KERNEL_ERROR_FILEERR;
return nullptr;
}
// Zeroed, since decryption and decompression can fill less than all of it and the ELF
// reader is handed the whole buffer.
newptr = new u8[maxElfSize]();
elfSize = maxElfSize;
ptr = newptr;
int decryptedSize = pspDecryptPRX(in, (u8*)ptr, head->psp_size, prxSeed, fuseId);
@@ -1722,13 +1735,13 @@ static PSPModule *__KernelLoadELFFromPtr(const u8 *ptr, size_t elfSize, u32 load
} else {
scheme = "gzip";
_dbg_assert_(Read32(ptr + 0x150) != ELF_MAGIC);
outBytes = gzipDecompress((u8 *)ptr, maxElfSize, temp);
outBytes = gzipDecompress((u8 *)ptr, maxElfSize, temp, decryptedSize);
}
free(temp);
if (outBytes < 0) {
// Bail out cleanly rather than falling through to parse whatever's left in the
// buffer (still compressed, not a valid ELF) as if it were real code.
*error_string = StringFromFormat("Module '%s' %s decompression failed", head->modname, scheme);
*error_string = StringFromFormat("Module '%s' %s decompression failed", headModName.c_str(), scheme);
delete [] newptr;
module->Cleanup();
kernelObjects.Destroy<PSPModule>(module->GetUID());
@@ -1736,7 +1749,7 @@ static PSPModule *__KernelLoadELFFromPtr(const u8 *ptr, size_t elfSize, u32 load
error = SCE_KERNEL_ERROR_FILEERR;
return nullptr;
}
INFO_LOG(Log::sceModule, "'%s' is %s-compressed, decompressing (%d -> %d bytes, bufmax=%d).", head->modname, scheme, decryptedSize, outBytes, maxElfSize);
INFO_LOG(Log::sceModule, "'%s' is %s-compressed, decompressing (%d -> %d bytes, bufmax=%d).", headModName.c_str(), scheme, decryptedSize, outBytes, maxElfSize);
}
if (fakeLoadedModule) {
@@ -1745,7 +1758,7 @@ static PSPModule *__KernelLoadELFFromPtr(const u8 *ptr, size_t elfSize, u32 load
std::string elfFilename(KeepAfterLast(filename, '/'));
if (elfFilename.empty() || startsWith(elfFilename, "sce_lbn")) {
// Use the name from the header.
elfFilename = head->modname;
elfFilename = headModName;
}
DumpFileIfEnabled(ptr, (u32)elfSize, elfFilename.c_str(), DumpFileType::PRX);
@@ -1884,7 +1897,7 @@ static PSPModule *__KernelLoadELFFromPtr(const u8 *ptr, size_t elfSize, u32 load
else
modinfoaddr = reader.GetSegmentVaddr(0) + (reader.GetSegmentPaddr(0) & 0x7FFFFFFF) - reader.GetSegmentOffset(0);
if (!Memory::IsValidAddress(modinfoaddr)) {
if (!Memory::IsValidRange(modinfoaddr, sizeof(PspModuleInfo))) {
*error_string = StringFromFormat("Bad module info address %08x", modinfoaddr);
ERROR_LOG(Log::sceModule, "Bad module info address %08x", modinfoaddr);
delete[] newptr;
@@ -1895,6 +1908,8 @@ static PSPModule *__KernelLoadELFFromPtr(const u8 *ptr, size_t elfSize, u32 load
}
modinfo = (const PspModuleInfo *)Memory::GetPointerUnchecked(modinfoaddr);
// Not necessarily terminated.
const std::string modInfoName(modinfo->name, strnlen(modinfo->name, sizeof(modinfo->name)));
module->nm.nsegment = reader.GetNumSegments();
module->nm.attribute = modinfo->moduleAttrs;
@@ -1902,9 +1917,9 @@ static PSPModule *__KernelLoadELFFromPtr(const u8 *ptr, size_t elfSize, u32 load
// name check is for firmware 1.50, whose vshmain.prx declares no attributes at all - Sony
// only started setting PSP_MODULE_VSH_MODE in 1.52. Without it the whole VSH bootstrap
// below was skipped and the shell ran with none of its support modules loaded.
if ((module->nm.attribute & PSP_MODULE_VSH_MODE) != 0 || equals(modinfo->name, "vsh_module")) {
if ((module->nm.attribute & PSP_MODULE_VSH_MODE) != 0 || modInfoName == "vsh_module") {
g_runningVSH = true;
INFO_LOG(Log::sceModule, "VSH mode module detected: %s", modinfo->name);
INFO_LOG(Log::sceModule, "VSH mode module detected: %s", modInfoName.c_str());
// The shell booted without a game: its drive starts out empty, for a disc to go in later.
if (!pspFileSystem.GetSystem("disc0:")) {
__UmdEject();
@@ -1913,7 +1928,7 @@ static PSPModule *__KernelLoadELFFromPtr(const u8 *ptr, size_t elfSize, u32 load
// OK, even if it's an ELF module, it might be one we shouldn't fully load and execute!
// This is seen with mpeg.prx in Tony Hawk's Underground 2, see #20568.
if (ShouldHLEModuleForLoad(modinfo->name)) {
if (ShouldHLEModuleForLoad(modInfoName)) {
// We load it, but at least we don't run any part of it.
module->isFake = true;
}
@@ -1988,7 +2003,7 @@ static PSPModule *__KernelLoadELFFromPtr(const u8 *ptr, size_t elfSize, u32 load
module->libstub = modinfo->libstub;
module->libstubend = modinfo->libstubend;
INFO_LOG(Log::Loader, "Module %s: %08x %08x %08x", modinfo->name, modinfo->gp, modinfo->libent, modinfo->libstub);
INFO_LOG(Log::Loader, "Module %s: %08x %08x %08x", modInfoName.c_str(), modinfo->gp, modinfo->libent, modinfo->libstub);
DEBUG_LOG(Log::Loader,"===================================================");
u32 firstImportStubAddr = 0;
@@ -2098,16 +2113,24 @@ static PSPModule *__KernelLoadELFFromPtr(const u8 *ptr, size_t elfSize, u32 load
const u32_le *entPos = nullptr;
const u32_le *entEnd = nullptr;
const u32_le *entBegin = nullptr;
// A module that wasn't relocated (cut off before its relocations, see LoadInto) still has
// link-time addresses here.
if (Memory::IsValidRange(modinfo->libent, modinfo->libentend - modinfo->libent)) {
entPos = (const u32_le *)Memory::GetPointerUnchecked(modinfo->libent);
entEnd = (const u32_le *)Memory::GetPointerUnchecked(modinfo->libentend);
entBegin = entPos;
} else {
WARN_LOG(Log::Loader, "Garbage libent address %08x or end %08x, no exports", modinfo->libent, modinfo->libentend);
}
for (int m = 0; entPos < entEnd; ++m) {
const u32 entAddr = modinfo->libent + (u32)((const u8 *)entPos - (const u8 *)entBegin);
// Read as a whole struct below, even when the entry is shorter.
if (!Memory::IsValidRange(entAddr, sizeof(PspLibEntEntry))) {
WARN_LOG_REPORT(Log::Loader, "Export entry at %08x runs off the end of memory", entAddr);
break;
}
const PspLibEntEntry *ent = (const PspLibEntEntry *)entPos;
entPos += ent->size;
if (ent->size == 0) {
@@ -2118,7 +2141,7 @@ static PSPModule *__KernelLoadELFFromPtr(const u8 *ptr, size_t elfSize, u32 load
const u32 variableCount = ent->size <= 4 ? ent->vcount : std::max((u32)ent->vcount , (u32)ent->vcountNew);
const char *name;
if (Memory::IsValidAddress(ent->name)) {
if (ent->name != 0 && Memory::IsValidNullTerminatedString(ent->name)) {
name = Memory::GetCharPointer(ent->name);
} else if (ent->name == 0) {
name = module->nm.name;
@@ -2128,9 +2151,11 @@ static PSPModule *__KernelLoadELFFromPtr(const u8 *ptr, size_t elfSize, u32 load
INFO_LOG(Log::Loader, "Exporting ent %d named %s, %d funcs, %d vars, resident %08x", m, name, ent->fcount, ent->vcount, ent->resident);
if (!Memory::IsValidAddress(ent->resident)) {
// The NIDs, then the addresses, one word each per function and variable.
const u32 residentBytes = ((u32)ent->fcount + variableCount) * 8;
if (!Memory::IsValidAddress(ent->resident) || !Memory::IsValidRange(ent->resident, residentBytes)) {
if (ent->fcount + variableCount > 0) {
WARN_LOG_REPORT(Log::Loader, "Invalid export resident address %08x", ent->resident);
WARN_LOG_REPORT(Log::Loader, "Invalid export resident address %08x (%d funcs, %d vars)", ent->resident, ent->fcount, variableCount);
}
continue;
}
@@ -2193,6 +2218,12 @@ static PSPModule *__KernelLoadELFFromPtr(const u8 *ptr, size_t elfSize, u32 load
WARN_LOG(Log::Loader, "Invalid var %d, nid %08x, export address %08x", j, nid, exportAddr);
continue;
}
// The thread parameter variables below are four words.
const bool isThreadParam = nid == NID_MODULE_START_THREAD_PARAMETER || nid == NID_MODULE_STOP_THREAD_PARAMETER || nid == NID_MODULE_REBOOT_BEFORE_THREAD_PARAMETER;
if ((isThreadParam && !Memory::IsValidRange(exportAddr, 16)) || (nid == NID_MODULE_SDK_VERSION && !Memory::IsValidRange(exportAddr, 4))) {
WARN_LOG(Log::Loader, "Var %d, nid %08x, export address %08x runs off the end of memory", j, nid, exportAddr);
continue;
}
int size;
switch (nid) {
@@ -2265,8 +2296,8 @@ static PSPModule *__KernelLoadELFFromPtr(const u8 *ptr, size_t elfSize, u32 load
delete [] newptr;
if (!reportedModule && ShouldHLEModuleForLoad(modinfo->name)) {
INFO_LOG(Log::sceModule, "Loading module %s with version %04x, devkit %08x", modinfo->name, modinfo->moduleVersion, devkitVersion);
if (!reportedModule && ShouldHLEModuleForLoad(modInfoName)) {
INFO_LOG(Log::sceModule, "Loading module %s with version %04x, devkit %08x", modInfoName.c_str(), modinfo->moduleVersion, devkitVersion);
if (!strcmp(modinfo->name, "sceMpeg_library")) {
__MpegLoadModule(modinfo->moduleVersion, module->crc);
@@ -2354,7 +2385,7 @@ SceUID KernelLoadModule(const std::string &filename, std::string *error_string,
u32 error = SCE_KERNEL_ERROR_ILLEGAL_OBJECT;
u32 magic;
PSPModule *module = __KernelLoadELFFromPtr(&buffer[0], buffer.size(), 0, fromTop, error_string, &magic, filename, error);
PSPModule *module = __KernelLoadELFFromPtr(buffer.data(), buffer.size(), 0, fromTop, error_string, &magic, filename, error);
if (module == nullptr)
return error;
@@ -2369,11 +2400,12 @@ static PSPModule *__KernelLoadModule(u8 *fileptr, size_t fileSize, SceKernelLMOp
// PBP!
u32_le version;
memcpy(&version, fileptr + 4, 4);
u32_le offset0, offsets[16];
u32_le offset0, offsets[16]{};
memcpy(&offset0, fileptr + 8, 4);
int numfiles = (offset0 - 8) / 4;
if (numfiles <= 0 || numfiles > 16) {
// offsets[5] and [6] are used below.
if (numfiles < 7 || numfiles > 16) {
*error_string = "Invalid PBP header - can't load";
return nullptr;
}
@@ -3686,11 +3718,13 @@ static u32 sceKernelLoadModuleByID(u32 id, u32 flags, u32 lmoptionPtr) {
std::string error_string;
pspFileSystem.SeekFile(handle, pos, FILEMOVE_BEGIN);
PSPModule *module = nullptr;
u8 *temp = new u8[size - pos];
pspFileSystem.ReadFile(handle, temp, size - pos);
// The game may have seeked past the end.
const size_t remaining = size > pos ? size - pos : 0;
u8 *temp = new u8[remaining]();
const size_t bytesRead = pspFileSystem.ReadFile(handle, temp, remaining);
u32 magic;
module = __KernelLoadELFFromPtr(temp, size - pos, 0, lmoption ? lmoption->position == PSP_SMEM_High : false, &error_string, &magic, "", error);
module = __KernelLoadELFFromPtr(temp, std::min(bytesRead, remaining), 0, lmoption ? lmoption->position == PSP_SMEM_High : false, &error_string, &magic, "", error);
delete [] temp;
if (!module) {
@@ -3805,6 +3839,9 @@ SceUID sceKernelLoadModuleBufferUsbWlan(u32 size, u32 bufPtr, u32 flags, u32 lmo
char fakeDebugFilename[512];
snprintf(fakeDebugFilename, sizeof(fakeDebugFilename), "moduleByPtr_%08x_%d", bufPtr, (int)size);
if (!Memory::IsValidRange(bufPtr, size)) {
return hleLogError(Log::Loader, SCE_KERNEL_ERROR_ILLEGAL_ADDR, "Bad module buffer %08x (%d bytes)", bufPtr, size);
}
module = __KernelLoadELFFromPtr(Memory::GetPointerOrException(bufPtr), size, 0, lmoption ? lmoption->position == PSP_SMEM_High : false, &error_string, &magic, fakeDebugFilename, error);
if (!module) {
+4
View File
@@ -678,6 +678,10 @@ void DetectZipFileContents(zip_t *z, ZipFileInfo *info) {
// is quite convenient and makes it easy to add shared heuristics.
for (int i = 0; i < numFiles; i++) {
const char *fn = zip_get_name(z, i, 0);
if (!fn) {
// A corrupt central directory entry.
continue;
}
zip_stat_t stat{};
zip_stat_index(z, i, 0, &stat);
+2 -1
View File
@@ -143,7 +143,8 @@ bool LoadParamSFOFromDisc() {
PSPFileInfo fileInfo = pspFileSystem.GetFileInfo(sfoPath.c_str());
if (fileInfo.exists) {
std::vector<u8> paramsfo;
pspFileSystem.ReadEntireFile(sfoPath, paramsfo);
// A real PARAM.SFO is a KB or two.
pspFileSystem.ReadEntireFile(sfoPath, paramsfo, false, 1024 * 1024);
if (g_paramSFO.ReadSFO(paramsfo)) {
g_paramSFORaw = g_paramSFO;
return true;
+9 -3
View File
@@ -15,6 +15,7 @@
// Official git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
#include <algorithm>
#include <cstring>
#include <ctime>
#include <vector>
@@ -482,8 +483,11 @@ uint32_t ReplayApplyDiskRead(void *data, uint32_t readSize, uint32_t dataSize, b
{
auto item = ReplayNextDisk(ReplayAction::FILE_READ, t);
if (item && item->data.size() <= dataSize) {
memcpy(data, &item->data[0], item->data.size());
return item->info.result;
if (!item->data.empty()) {
memcpy(data, item->data.data(), item->data.size());
}
// The game sizes its next steps by this, so don't claim more than was copied.
return std::min((uint32_t)item->info.result, (uint32_t)item->data.size());
}
return readSize;
}
@@ -492,7 +496,9 @@ uint32_t ReplayApplyDiskRead(void *data, uint32_t readSize, uint32_t dataSize, b
{
ReplayItem item = ReplayItemHeader(ReplayAction::FILE_READ, t, readSize);
item.data.resize(readSize);
memcpy(&item.data[0], data, readSize);
if (readSize) {
memcpy(item.data.data(), data, readSize);
}
replayItems.push_back(item);
return readSize;
}
+2 -1
View File
@@ -122,7 +122,8 @@ enum class OperationType {
struct PendingRead {
std::atomic<bool> done{ false };
CChunkFileReader::Error error = CChunkFileReader::ERROR_NONE;
std::unique_ptr<u8[]> buffer;
// From LoadFile, so malloc'd.
std::unique_ptr<u8, decltype(&free)> buffer{ nullptr, &free };
size_t size = 0;
std::string gitVersion;
// "PPSSPP <version>" from the header, for the failure message.
+9 -2
View File
@@ -179,7 +179,9 @@ int AnalyzeAtracTrack(const u8 *buffer, u32 size, Track *track, std::string *err
}
case FACT_CHUNK_MAGIC:
{
track->endSample = Read32(buffer, offset);
if (chunkSize >= 4) {
track->endSample = Read32(buffer, offset);
}
if (chunkSize >= 8) {
track->firstSampleOffset = Read32(buffer, offset + 4);
}
@@ -417,6 +419,10 @@ int ParseWaveAT3(const u8 *data, u32 dataLength, TrackInfo *track) {
}
const u32 chunkID = Read32(data, &offset);
const int chunkSize = RoundUpToEven(Read32(data, &offset));
// A negative size would point back at this chunk's own header, looping forever.
if (chunkSize < 0) {
return SCE_ERROR_ATRAC_SIZE_TOO_SMALL;
}
const int nextOffset = offset + chunkSize;
// We allow the data chunk to be bigger than readSize, as we may not have read the whole file.
if ((u32)(offset + chunkSize) > dataLength && chunkID != DATA_CHUNK_MAGIC) {
@@ -634,7 +640,8 @@ static u32 ParseAA3Headers(u32 readSize, AA3Info *info, u32 fileSize, const u8 *
offset = 0;
}
if ((u32)offset + 0x22 > readSize) {
// The header fields read below go up to offset + 0x24.
if ((u32)offset + 0x24 > readSize) {
return SCE_ERROR_ATRAC_AA3_SIZE_TOO_SMALL;
}
if (Parse3BytesBE(aa3Data, &offset) != 0x454133) {
+15 -3
View File
@@ -912,7 +912,8 @@ bool GameManager::InstallZippedISO(struct zip *z, int isoFileIndex, const Path &
return false;
}
std::string fn = fnPtr;
size_t nameOffset = fn.rfind('/');
// Zips made on Windows can use either separator.
size_t nameOffset = fn.find_last_of("/\\");
if (nameOffset == std::string::npos) {
nameOffset = 0;
} else {
@@ -934,6 +935,13 @@ bool GameManager::InstallZippedISO(struct zip *z, int isoFileIndex, const Path &
name = name.substr(2);
}
// The name is used as a filename in the destination, so mustn't be able to point outside it.
if (!IsSafePathComponent(name)) {
ERROR_LOG(Log::IO, "Bad ISO filename in zip: '%s'", fn.c_str());
SetInstallError(I18NCat::INSTALLZIP, "Zip archive corrupt");
return false;
}
Path outputISOFilename = destDir;
if (outputISOFilename.empty()) {
outputISOFilename = Path(g_Config.currentDirectory);
@@ -952,8 +960,12 @@ bool GameManager::InstallZippedISO(struct zip *z, int isoFileIndex, const Path &
installProgress_ = 1.0f;
InstallDone();
ResetInstallError();
return true;
if (success) {
ResetInstallError();
} else {
SetInstallError(I18NCat::INSTALLZIP, "Installation failed");
}
return success;
}
bool GameManager::InstallZippedPS1Disc(struct zip *z, const std::vector<std::string> &files, const Path &destDir) {
+5
View File
@@ -697,6 +697,11 @@ int PSARReader::NextEntry(std::string *error) {
const u32 chunkSize = ReadU32(block_.data() + 0x104);
const u32 expandedSize = ReadU32(block_.data() + 0x108);
// pos_ is a u32, so a huge chunk would wrap it back to an earlier entry and loop forever.
if (chunkSize > limit_ - std::min((size_t)pos_, limit_)) {
*error = StringFromFormat("Entry '%s' runs past the end of the archive (%u bytes)", entryName_.c_str(), chunkSize);
return -1;
}
if (expandedSize == 0) {
entryIsDirectory_ = true;
+1 -1
View File
@@ -187,7 +187,7 @@ bool PkgReader::Open(FileLoader *loader, std::string *error) {
info_.contentId = contentId;
const s64 fileSize = loader->FileSize();
if (fileSize < 0 || (u64)fileSize < totalSize || dataOffset_ + dataSize_ > (u64)fileSize) {
if (fileSize < 0 || (u64)fileSize < totalSize || dataOffset_ > (u64)fileSize || dataSize_ > (u64)fileSize - dataOffset_) {
*error = "PKG file is truncated";
return false;
}
+16 -7
View File
@@ -553,6 +553,12 @@ ReplacedTexture::LoadLevelResult ReplacedTexture::LoadLevelData(VFSFileReference
ERROR_LOG(Log::TexReplacement, "Could not load texture replacement info: %s - unsupported format %s", filename.c_str(), magic.c_str());
}
// The sizes come straight from the file headers, and everything below sizes buffers from them.
if (good && (level.w <= 0 || level.h <= 0 || level.w > 8192 || level.h > 8192)) {
ERROR_LOG(Log::TexReplacement, "Replacement texture %s has a bad size: %dx%d", filename.c_str(), level.w, level.h);
good = false;
}
// TODO: We no longer really need to have a split in this function, the upper and lower parts can be merged now.
if (good && mipLevel != 0) {
@@ -723,19 +729,22 @@ ReplacedTexture::LoadLevelResult ReplacedTexture::LoadLevelData(VFSFileReference
bool result = transcoder.get_image_level_info(levelInfo, i, 0, 0);
_dbg_assert_(result);
size_t dataSizeBytes = levelInfo.m_total_blocks * blockSize;
size_t dataSizeBytes = (size_t)levelInfo.m_total_blocks * blockSize;
size_t outputSize = levelInfo.m_total_blocks;
size_t outputPitch = levelInfo.m_num_blocks_x;
// Support transcoded-to-RGBA8888 images too.
if (!bc) {
dataSizeBytes = levelInfo.m_orig_width * levelInfo.m_orig_height * 4;
outputSize = levelInfo.m_orig_width * levelInfo.m_orig_height;
dataSizeBytes = (size_t)levelInfo.m_orig_width * levelInfo.m_orig_height * 4;
outputSize = (size_t)levelInfo.m_orig_width * levelInfo.m_orig_height;
outputPitch = levelInfo.m_orig_width;
}
out.resize(dataSizeBytes);
transcodeState.clear();
transcoder.transcode_image_level(i, 0, 0, &out[0], (uint32_t)outputSize, transcoderFormat, 0, (uint32_t)outputPitch, level.h, -1, -1, &transcodeState);
if (!result || dataSizeBytes == 0 || !transcoder.transcode_image_level(i, 0, 0, &out[0], (uint32_t)outputSize, transcoderFormat, 0, (uint32_t)outputPitch, levelInfo.m_orig_height, -1, -1, &transcodeState)) {
WARN_LOG(Log::TexReplacement, "KTX2: Failed to transcode level %d of %s", i, filename.c_str());
std::fill(out.begin(), out.end(), 0);
}
level.w = levelInfo.m_orig_width;
level.h = levelInfo.m_orig_height;
if (i != 0)
@@ -822,15 +831,15 @@ ReplacedTexture::LoadLevelResult ReplacedTexture::LoadLevelData(VFSFileReference
// We only use the first level for now.
int numLevels = LoadZIMPtr(&zim[0], fileSize, w, h, &f, image);
if (numLevels > 0) {
if (w[0] > level.w || h[0] > level.h) {
if (w[0] > level.w || h[0] > level.h || (f & ZIM_FORMAT_MASK) != ZIM_RGBA8888) {
ERROR_LOG(Log::TexReplacement, "Texture replacement changed since header read: %s", filename.c_str());
free(image[0]);
return LoadLevelResult::LOAD_ERROR;
}
out.resize(level.w * level.h * 4);
out.resize((size_t)level.w * level.h * 4);
if (w[0] == level.w) {
memcpy(&out[0], image[0], level.w * 4 * level.h);
memcpy(&out[0], image[0], (size_t)level.w * 4 * h[0]);
} else {
for (int y = 0; y < h[0]; ++y) {
memcpy(&out[level.w * 4 * y], image[0] + w[0] * 4 * y, w[0] * 4);
+6 -2
View File
@@ -367,6 +367,9 @@ bool TextureReplacer::LoadIniValues(IniFile &ini, VFSBackend *dir, bool isOverri
}
options->Get("skipLastDXT1Blocks128x64", &skipLastDXT1Blocks128x64_);
options->Get("skipLastDXT1Blocks128x128", &skipLastDXT1Blocks128x128_);
// These are subtracted from the hashed size, so can't be more than the blocks there are.
skipLastDXT1Blocks128x64_ = std::clamp(skipLastDXT1Blocks128x64_, 0, (128 / 4) * (64 / 4));
skipLastDXT1Blocks128x128_ = std::clamp(skipLastDXT1Blocks128x128_, 0, (128 / 4) * (128 / 4));
if (reduceHash_ && textureHash_ == ReplacedTextureHash::QUICK) {
reduceHash_ = false;
ERROR_LOG(Log::TexReplacement, "Texture Replacement: reduceHash option requires safer hash, use xxh32 or xxh64 instead.");
@@ -635,11 +638,12 @@ u32 TextureReplacer::ComputeHash(u32 addr, int bufw, int w, int h, bool swizzled
}
// Hack for Yu Gi Oh texture hashing problem. See issue #19714
if (skipLastDXT1Blocks128x64_ && fmt == GE_TFMT_DXT1 && w == 128 && h == 64) {
// (reduceHash can make sizeInRAM smaller than all the blocks, so check it can't underflow.)
if (skipLastDXT1Blocks128x64_ && fmt == GE_TFMT_DXT1 && w == 128 && h == 64 && (u32)(8 * skipLastDXT1Blocks128x64_) < (u32)sizeInRAM) {
// Skip the last few blocks as specified.
sizeInRAM -= 8 * skipLastDXT1Blocks128x64_;
}
if (skipLastDXT1Blocks128x128_ && fmt == GE_TFMT_DXT1 && w == 128 && h == 128) {
if (skipLastDXT1Blocks128x128_ && fmt == GE_TFMT_DXT1 && w == 128 && h == 128 && (u32)(8 * skipLastDXT1Blocks128x128_) < (u32)sizeInRAM) {
sizeInRAM -= 8 * skipLastDXT1Blocks128x128_;
}
+20 -5
View File
@@ -2,6 +2,7 @@
#include <string>
#include <mutex>
#include <algorithm>
#include <climits>
#include "Common/File/VFS/VFS.h"
#include "Common/UI/Root.h"
@@ -126,19 +127,25 @@ bool WavData::Read(RIFFReader &file_) {
int playCount;
};
if (numLoops > 0 && smplData.size() >= 36 + sizeof(AtracLoopInfo) * numLoops) {
// Divided, since the product wraps a 32-bit size_t.
if (numLoops > 0 && smplData.size() >= 36 && (smplData.size() - 36) / sizeof(AtracLoopInfo) >= (size_t)numLoops) {
AtracLoopInfo *loops = (AtracLoopInfo *)&smplData[36];
int samplesPerFrame = codec == PSP_CODEC_AT3PLUS ? 2048 : 1024;
for (int i = 0; i < numLoops; ++i) {
// Only seen forward loops, so let's ignore others.
if (loops[i].type != 0)
// Only seen forward loops, so let's ignore others. Also ones that don't go forwards,
// which would point playback before the data.
if (loops[i].type != 0 || loops[i].startSample < 0 || loops[i].endSample <= loops[i].startSample)
continue;
// We ignore loop interpolation (fraction) and play count for now.
raw_offset_loop_start = (loops[i].startSample / samplesPerFrame) * raw_bytes_per_frame;
const s64 loopStart = (s64)(loops[i].startSample / samplesPerFrame) * raw_bytes_per_frame;
const s64 loopEnd = (s64)(loops[i].endSample / samplesPerFrame) * raw_bytes_per_frame;
if (loopEnd > INT_MAX)
continue;
raw_offset_loop_start = (int)loopStart;
loop_start_offset = loops[i].startSample % samplesPerFrame;
raw_offset_loop_end = (loops[i].endSample / samplesPerFrame) * raw_bytes_per_frame;
raw_offset_loop_end = (int)loopEnd;
loop_end_offset = loops[i].endSample % samplesPerFrame;
if (loops[i].playCount == 0) {
@@ -200,6 +207,14 @@ bool WavData::Read(RIFFReader &file_) {
ERROR_LOG(Log::Audio, "Could not descend into RIFF file.");
return false;
}
// The loop points came from the smpl chunk, so they have to land in the data. The sample
// offsets (within a frame) are always positive, since the loop samples were checked above.
if (raw_offset_loop_start < 0 || raw_offset_loop_end > raw_data_size || raw_offset_loop_start > raw_offset_loop_end) {
raw_offset_loop_start = 0;
raw_offset_loop_end = 0;
loop_start_offset = 0;
loop_end_offset = 0;
}
sample_rate = samplesPerSec;
return true;
}
+1 -1
View File
@@ -298,7 +298,7 @@ bool ImposeArt::Parse(const std::vector<uint8_t> &file, int pspLanguage) {
const uint32_t w = r.U32(rec + 16);
const uint32_t h = r.U32(rec + 20);
const uint32_t offset = r.U32(rec + 24);
if (!r.ok || palette >= NUM_PALETTES || w == 0 || h == 0 || x + w > PSP_W || y + h > PSP_H || (w * h + 1) / 2 != size) {
if (!r.ok || palette >= NUM_PALETTES || w == 0 || h == 0 || w > PSP_W || x > PSP_W - w || h > PSP_H || y > PSP_H - h || (w * h + 1) / 2 != size) {
return false;
}
const size_t pixels = block + TEXT_RECORDS + (size_t)offset;
+3 -1
View File
@@ -448,7 +448,9 @@ void StoreScreen::ParseListing(const std::string &json) {
// (Path::operator/ does no ".." normalization); on platforms/builds where
// the store index isn't fetched over HTTPS, a MITM'd or compromised
// response could otherwise point "launch" at an arbitrary host path.
if (HasParentDirComponent(file))
// It's also uninstalled (deleted recursively) by name, so "." or "/" would be the
// whole GAME directory.
if (HasParentDirComponent(file) || !IsSafePathComponent(file))
continue;
e.file = file;
entries_.push_back(e);
+8 -2
View File
@@ -3188,12 +3188,18 @@ namespace jpgd {
*height = image_height;
*actual_comps = decoder.get_num_components();
// PPSSPP: Nothing we decode is anywhere near this, and the header alone (from a game, or
// a file) could otherwise make us allocate gigabytes.
if (image_width <= 0 || image_height <= 0 || image_width > 8192 || image_height > 8192)
return nullptr;
if (decoder.begin_decoding() != JPGD_SUCCESS)
return nullptr;
const int dst_bpl = image_width * req_comps;
uint8* pImage_data = (uint8*)jpgd_malloc(dst_bpl * image_height);
// PPSSPP: In size_t, the int product overflows for large (up to 32768x32768) images.
uint8* pImage_data = (uint8*)jpgd_malloc((size_t)dst_bpl * image_height);
if (!pImage_data)
return nullptr;
@@ -3207,7 +3213,7 @@ namespace jpgd {
return nullptr;
}
uint8* pDst = pImage_data + y * dst_bpl;
uint8* pDst = pImage_data + (size_t)y * dst_bpl;
if (((req_comps == 1) && (decoder.get_num_components() == 1)) || ((req_comps == 4) && (decoder.get_num_components() == 3)))
memcpy(pDst, pScan_line, dst_bpl);
+5 -2
View File
@@ -154,7 +154,8 @@ int sceDrmBBMacUpdate(KirkState *kirk, MAC_KEY *mkey, u8 *buf, int size)
int retv = 0, ksize, p, type;
u8 *kbuf;
if(mkey->pad_size>16){
// The key can live in guest memory (sceDrmBBMacUpdate), so pad_size can be anything.
if(mkey->pad_size<0 || mkey->pad_size>16){
retv = 0x80510302;
goto _exit;
}
@@ -204,7 +205,7 @@ int sceDrmBBMacFinal(KirkState *kirk, MAC_KEY *mkey, u8 *buf, u8 *vkey)
u8 *kbuf, tmp[16], tmp1[16];
u32 t0, v0, v1;
if(mkey->pad_size>16)
if(mkey->pad_size<0 || mkey->pad_size>16)
return 0x80510302;
code = (mkey->type==2)? 0x3A : 0x38;
@@ -609,6 +610,8 @@ PGD_DESC *pgd_open(KirkState *kirk, u8 *pgd_buf, int pgd_flag, u8 *pgd_vkey)
//DEBUG_LOG(Log::HLE, "Open PGD ...");
pgd = (PGD_DESC*)malloc(sizeof(PGD_DESC));
if (!pgd)
return NULL;
memset(pgd, 0, sizeof(PGD_DESC));
pgd->key_index = *(u32*)(pgd_buf+4);
+7
View File
@@ -403,6 +403,13 @@ double ScreenshotComparer::Compare(const Path &screenshotFilename) {
return -1.0f;
}
// Everything below reads w_ x h_ pixels from it.
if (width < (int)w_ || height < (int)h_) {
error_ = StringFromFormat("Screenshot is too small (%dx%d, expected %dx%d): %s", width, height, (int)w_, (int)h_, screenshotFilename.ToVisualString().c_str());
free(reference_);
reference_ = nullptr;
return -1.0f;
}
referenceStride_ = width;
}
} else {