Texture packs, shaders: Bound what shared content can ask for

- textures.ini hashranges with a source over 1024 are rejected. A height
  of 65536 or more spilled into the width bits of the key, matching a real
  texture with a range bigger than it, which overflowed the upload buffer
  in CopyLevelTo and the save buffer in NotifyTextureDecoded. Both now
  also check the sizes themselves.
- A '|' in a textures.ini filename split into another mip level after the
  parent dir check, so it's rejected.
- Post-processing and texture shader inis can't point outside their own
  folder, and a texture shader's ConstantBuffer must be a valid uniform
  buffer size.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Henrik RydgårdandClaude Opus 5.5 committed 2026-11-05 21:21:22 +01:00
1 parent 05cca98c31
commit ceab9b7a3f
4 files changed
+59 -13

No files matched your search

+27 -7
View File
@@ -31,6 +31,7 @@
#include "Common/StringUtils.h"
#include "Core/System.h"
#include "Core/Util/PathUtil.h"
#include "GPU/Common/PostShader.h"
static std::vector<ShaderInfo> shaderInfo;
@@ -154,12 +155,17 @@ void LoadPostShaderInfo(Draw::DrawContext *draw, const std::vector<Path> &direct
section.Get("Name", &info.name);
section.Get("Parent", &info.parent);
section.Get("Visible", &info.visible);
temp.clear();
section.Get("Fragment", &temp);
info.fragmentShaderFile = path / temp;
temp.clear();
section.Get("Vertex", &temp);
info.vertexShaderFile = path / temp;
std::string fragment;
std::string vertex;
section.Get("Fragment", &fragment);
section.Get("Vertex", &vertex);
// Shader inis come from downloads, so keep them to their own folder.
if (HasParentDirComponent(fragment) || HasParentDirComponent(vertex)) {
WARN_LOG(Log::G3D, "Ignoring shader '%s' with a parent dir in its paths", info.section.c_str());
continue;
}
info.fragmentShaderFile = path / fragment;
info.vertexShaderFile = path / vertex;
section.Get("OutputResolution", &info.outputResolution);
section.Get("Upscaling", &info.isUpscalingFilter);
section.Get("SSAA", &info.SSAAFilterLevel);
@@ -221,19 +227,33 @@ void LoadPostShaderInfo(Draw::DrawContext *draw, const std::vector<Path> &direct
section.Get("Hidden", &info.hidden);
std::string cbufferFilename;
if (section.Get("ConstantBuffer", &cbufferFilename)) {
if (HasParentDirComponent(cbufferFilename)) {
WARN_LOG(Log::G3D, "Ignoring texture shader '%s' with a parent dir in its paths", info.section.c_str());
continue;
}
Path cbufferPath = path / cbufferFilename;
info.constantBuffer = cbufferPath;
}
if (section.Get("Compute", &temp)) {
if (HasParentDirComponent(temp)) {
WARN_LOG(Log::G3D, "Ignoring texture shader '%s' with a parent dir in its paths", info.section.c_str());
continue;
}
info.computeShaderFile = path / temp;
info.computeShaderFiles.push_back(info.computeShaderFile);
bool badPath = false;
for (int computeIndex = 2; computeIndex <= 4; ++computeIndex) {
temp.clear();
if (section.Get(StringFromFormat("Compute%d", computeIndex).c_str(), &temp)) {
if (HasParentDirComponent(temp)) {
badPath = true;
}
info.computeShaderFiles.push_back(path / temp);
}
}
if (info.scaleFactor >= 2 && info.scaleFactor < 8) {
if (badPath) {
WARN_LOG(Log::G3D, "Ignoring texture shader '%s' with a parent dir in its paths", info.section.c_str());
} else if (info.scaleFactor >= 2 && info.scaleFactor < 8) {
appendTextureShader(info);
}
} else {
+13 -1
View File
@@ -1015,9 +1015,21 @@ bool ReplacedTexture::CopyLevelTo(int level, uint8_t *out, size_t outDataSize, i
WARN_LOG(Log::TexReplacement, "Level %d is empty", level);
return false;
}
if (info.w > outW || info.h > outH) {
WARN_LOG(Log::TexReplacement, "Level %d (%dx%d) is bigger than its padded size %dx%d", level, info.w, info.h, outW, outH);
return false;
}
int blockSize;
if (!Draw::DataFormatIsBlockCompressed(fmt, &blockSize)) {
const bool blockCompressed = Draw::DataFormatIsBlockCompressed(fmt, &blockSize);
const size_t needed = blockCompressed ? (size_t)((outW + 3) / 4) * ((outH + 3) / 4) * blockSize : (size_t)rowPitch * (outH - 1) + outW * 4;
// A zero size disables the check, like for the other copies.
if (outDataSize != 0 && outDataSize < needed) {
WARN_LOG(Log::TexReplacement, "Level %d doesn't fit the buffer (%d < %d)", level, (int)outDataSize, (int)needed);
return false;
}
if (!blockCompressed) {
if (fmt != Draw::DataFormat::R8G8B8A8_UNORM) {
ERROR_LOG(Log::TexReplacement, "Unexpected linear data format");
return false;
+12 -5
View File
@@ -413,8 +413,9 @@ bool TextureReplacer::LoadIniValues(IniFile &ini, VFSBackend *dir, bool isOverri
std::string_view v = line.Value();
if (sscanf(k, "%16llx%8x_%d", &key.cachekey, &key.hash, &level) >= 1) {
// Reject path traversal: a "../" component could make us read
// or write files outside the texture pack directory.
if (HasParentDirComponent(v)) {
// or write files outside the texture pack directory. '|' separates mip levels in the alias,
// so it would let a later level skip this check.
if (HasParentDirComponent(v) || v.find('|') != std::string_view::npos) {
ERROR_LOG(Log::TexReplacement, "Ignoring texture filename with parent dir component: %s", std::string(v).c_str());
continue;
}
@@ -521,6 +522,12 @@ void TextureReplacer::ParseHashRange(const std::string &key, const std::string &
return;
}
// Generous, PSP textures are at most 512x512. Values of 65536 or more would also spill into the other fields of the key.
if (fromW > 1024 || fromH > 1024) {
ERROR_LOG(Log::TexReplacement, "Ignoring invalid hashrange %s = %s, source bigger than 1024x1024", key.c_str(), value.c_str());
return;
}
if (toW > fromW || toH > fromH) {
ERROR_LOG(Log::TexReplacement, "Ignoring invalid hashrange %s = %s, range bigger than source", key.c_str(), value.c_str());
return;
@@ -934,13 +941,13 @@ void TextureReplacer::NotifyTextureDecoded(ReplacedTexture *texture, const Repla
// Only save the hashed portion of the PNG, in case we have specified it in the ini file.
int lookupW;
int lookupH;
if (LookupHashRange(replacedInfo.addr, origW, origH, &lookupW, &lookupH)) {
if (LookupHashRange(replacedInfo.addr, origW, origH, &lookupW, &lookupH) && lookupW > 0 && lookupH > 0 && lookupW <= origW && lookupH <= origH) {
w = lookupW * (scaledW / origW);
h = lookupH * (scaledH / origH);
}
constexpr int bpp = 4;
size_t saveBufSize = w * h * bpp;
size_t saveBufSize = (size_t)w * h * bpp;
u8 *saveBuf = (u8 *)malloc(saveBufSize);
if (!saveBuf) {
ERROR_LOG(Log::TexReplacement, "Failed to allocated %d bytes of memory for saving a texture", (int)saveBufSize);
@@ -950,7 +957,7 @@ void TextureReplacer::NotifyTextureDecoded(ReplacedTexture *texture, const Repla
// Copy data to a buffer so we can send it to the thread. Might as well compact-away the pitch
// while we're at it.
for (int y = 0; y < h; y++) {
memcpy(saveBuf + y * w * bpp, (const u8 *)data + y * srcPitch, w * bpp);
memcpy(saveBuf + (size_t)y * w * bpp, (const u8 *)data + (size_t)y * srcPitch, w * bpp);
}
SaveTextureTask *task = new SaveTextureTask(std::move(saveBuf));
+7
View File
@@ -366,6 +366,13 @@ bool TextureCacheVulkan::LoadConstantBuffer(VulkanContext *vulkan, VkCommandBuff
ClearScalingShaders(vulkan);
return false;
}
// The file comes with a downloaded shader, and has to make a valid uniform buffer.
if (constantsSize < 16 || (constantsSize & 15) != 0 || constantsSize > vulkan->GetPhysicalDeviceProperties().properties.limits.maxUniformBufferRange) {
ERROR_LOG(Log::G3D, "Constant buffer file '%s' has a bad size (%d) - disabling hardware scaling", cbufferPath_.c_str(), (int)constantsSize);
delete[] contents;
ClearScalingShaders(vulkan);
return false;
}
textureScaleCBuffer_.Create(vulkan, "TextureScale CBuffer", constantsSize, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT);
VulkanPushPool *pushPool = drawEngine_->GetPushBufferForTextureData();
VkBuffer srcBuf = VK_NULL_HANDLE;