Merge pull request #22189 from hrydgard/debug-input-rewind-overflows

Fix three more out-of-bounds writes (minor)
This commit is contained in:
Henrik Rydgård authored and GitHub committed 2026-08-31 17:53:10 +02:00
commit 56bba5f6f5
8 files changed
+46 -40

No files matched your search

-23
View File
@@ -371,29 +371,6 @@ bool ConfigSetting::RestoreToDefault(ConfigBlock *configBlock, bool log) const {
return false;
}
// Might be used to copy individual settings from defaulted blocks. Didn't end up using this for now.
void ConfigSetting::CopyFromBlock(const ConfigBlock *other) {
_dbg_assert_(offset_ >= 0 && offset_ < other->Size());
const char *otherOwner = (const char *)other;
const char *thisOwner = (const char *)this;
switch (type_) {
case Type::TYPE_BOOL: *(bool *)(thisOwner + offset_) = *(const bool *)(otherOwner + offset_); break;
case Type::TYPE_INT: *(int *)(thisOwner + offset_) = *(const int *)(otherOwner + offset_); break;
case Type::TYPE_UINT32: *(uint32_t *)(thisOwner + offset_) = *(const uint32_t *)(otherOwner + offset_); break;
case Type::TYPE_UINT64: *(uint64_t *)(thisOwner + offset_) = *(const uint64_t *)(otherOwner + offset_); break;
case Type::TYPE_FLOAT: *(float *)(thisOwner + offset_) = *(const float *)(otherOwner + offset_); break;
case Type::TYPE_STRING: *(std::string *)(thisOwner + offset_) = *(const std::string *)(otherOwner + offset_); break;
case Type::TYPE_STRING_VECTOR: *(std::vector<std::string> *)(thisOwner + offset_) = *(const std::vector<std::string> *)(otherOwner + offset_); break;
case Type::TYPE_PATH: *(Path *)(thisOwner + offset_) = *(const Path *)(otherOwner + offset_); break;
case Type::TYPE_TOUCH_POS: *(ConfigTouchPos *)(thisOwner + offset_) = *(const ConfigTouchPos *)(otherOwner + offset_); break;
case Type::TYPE_CUSTOM_BUTTON: *(ConfigCustomButton *)(thisOwner + offset_) = *(const ConfigCustomButton *)(otherOwner + offset_); break;
default:
_dbg_assert_msg_(false, "CopyFromBlock(%.*s): Unexpected setting type: %d", STR_VIEW(iniKey_), (int)type_);
return;
}
}
void ConfigSetting::ReportSetting(const ConfigBlock *configBlock, UrlEncoder &data, const std::string &prefix) const {
if (!Report())
return;
-2
View File
@@ -190,8 +190,6 @@ struct ConfigSetting {
bool ReadFromIniSection(ConfigBlock *configBlock, const Section *section, bool applyDefaultIfMissing) const;
void CopyFromBlock(const ConfigBlock *other);
// Yes, this can be const because what's modified is not the ConfigSetting struct, but the value which is stored elsewhere.
// Should actually be called WriteToIni or something.
void WriteToIniSection(const ConfigBlock *configBlock, Section *section) const;
+4 -1
View File
@@ -683,7 +683,10 @@ void ControlMapper::Axis(const AxisInput *axes, size_t count) {
if (deviceIndex < (size_t)DEVICE_ID_COUNT) {
deviceTimestamps_[deviceIndex] = now;
}
rawAxisValue_[axis.axisId] = axis.value; // these are only used for co-axis mapping
// Same as deviceId above, axisId comes straight from the device and can be out of range.
if ((size_t)axis.axisId < JOYSTICK_AXIS_MAX) {
rawAxisValue_[axis.axisId] = axis.value; // these are only used for co-axis mapping
}
if (axis.value >= 0.0f) {
InputMapping mapping(axis.deviceId, axis.axisId, 1);
InputMapping opposite(axis.deviceId, axis.axisId, -1);
+22 -3
View File
@@ -29,6 +29,7 @@ CChunkFileReader::Error StateRingbuffer::Save() {
{
base_ = (base_ + 1) % ARRAY_SIZE(bases_);
baseUsage_ = 0;
baseGeneration_[base_] = nextBaseGeneration_++;
err = SaveToRam(bases_[base_]);
// Let's not bother savestating twice.
compressBuffer = &bases_[base_];
@@ -42,7 +43,7 @@ CChunkFileReader::Error StateRingbuffer::Save() {
states_[n].clear();
}
baseMapping_[n] = base_;
baseMapping_[n] = baseGeneration_[base_];
return err;
}
@@ -59,8 +60,17 @@ CChunkFileReader::Error StateRingbuffer::Restore(std::string *errorString, std::
auto pa = GetI18NCategory(I18NCat::PAUSE);
const int generation = baseMapping_[n];
const int baseSlot = generation < 0 ? -1 : generation % (int)ARRAY_SIZE(bases_);
if (baseSlot < 0 || baseGeneration_[baseSlot] != generation) {
// The base this state was compressed against has since been overwritten, so it can't be
// decoded any more. Only two bases are kept, but the state ring is longer.
WARN_LOG(Log::SaveState, "Rewind: state %d was compressed against a base that's gone", n);
return CChunkFileReader::ERROR_BAD_FILE;
}
static std::vector<u8> buffer;
LockedDecompress(buffer, states_[n].stateBuffer, bases_[baseMapping_[n]]);
LockedDecompress(buffer, states_[n].stateBuffer, bases_[baseSlot]);
CChunkFileReader::Error error = LoadFromRam(buffer, errorString);
*metadata = pa->T("Rewind");
@@ -117,7 +127,12 @@ void StateRingbuffer::LockedDecompress(std::vector<u8> &result, const std::vecto
for (size_t i = 0; i < compressed.size(); ) {
if (compressed[i] == 0) {
++i;
int blockSize = std::min(BLOCK_SIZE, (int)(base.size() - result.size()));
// Bound against what's actually left of the base: the subtraction this used to do
// (base.size() - result.size()) wraps once the output is longer than the base.
const int blockSize = (int)std::min((size_t)BLOCK_SIZE, (size_t)(base.end() - basePos));
if (blockSize <= 0) {
break;
}
result.insert(result.end(), basePos, basePos + blockSize);
basePos += blockSize;
} else {
@@ -145,6 +160,10 @@ void StateRingbuffer::Clear() {
for (auto &b : bases_) {
b.clear();
}
for (int &g : baseGeneration_) {
g = -1;
}
nextBaseGeneration_ = 0;
baseMapping_.clear();
baseMapping_.resize(size_);
for (auto &s : states_) {
+4
View File
@@ -65,6 +65,10 @@ private:
std::vector<RewindState> states_;
StateBuffer bases_[2];
// Which generation each base slot currently holds, and which generation each state was
// compressed against. There are more states than bases, so states do go stale.
int baseGeneration_[2] = {-1, -1};
int nextBaseGeneration_ = 0;
std::vector<int> baseMapping_;
std::mutex lock_;
std::thread compressThread_;
+10 -5
View File
@@ -955,7 +955,8 @@ static SoftwareTransformAction ProjectClipAndExpand(SoftwareTransformParams &par
static bool ExpandRectangles(int vertexCount, int &numDecodedVerts, int vertsSize, u16 *&inds, int indsSize, const TransformedVertex *transformed, TransformedVertex *transformedExpanded, int *drawIndexCount, bool throughmode, bool *pixelMappedExactly) {
// Before we start, do a sanity check - does the output fit?
if ((vertexCount / 2) * 6 > indsSize) {
// The expanded indices are written after the input ones, at inds + vertexCount.
if (vertexCount + (vertexCount / 2) * 6 > indsSize) {
// Won't fit, kill the draw.
return false;
}
@@ -1128,7 +1129,8 @@ void IndexBufferProvokingLastToFirst(int prim, u16 *inds, int indsSize) {
static bool ExpandLines(int vertexCount, int &numDecodedVerts, int vertsSize, u16 *&inds, int indsSize, const TransformedVertex *transformed, TransformedVertex *transformedExpanded, int *drawIndexCount, bool throughmode) {
// Before we start, do a sanity check - does the output fit?
if ((vertexCount / 2) * 6 > indsSize) {
// The expanded indices are written after the input ones, at inds + vertexCount.
if (vertexCount + (vertexCount / 2) * 6 > indsSize) {
// Won't fit, kill the draw.
return false;
}
@@ -1261,7 +1263,8 @@ static bool ExpandLines(int vertexCount, int &numDecodedVerts, int vertsSize, u1
static bool ExpandPoints(int vertexCount, int &maxIndex, int vertsSize, u16 *&inds, int indsSize, const TransformedVertex *transformed, TransformedVertex *transformedExpanded, int *drawIndexCount, bool throughmode, float pointScale) {
// Before we start, do a sanity check - does the output fit?
if (vertexCount * 6 > indsSize) {
// The expanded indices are written after the input ones, at inds + vertexCount.
if (vertexCount + vertexCount * 6 > indsSize) {
// Won't fit, kill the draw.
return false;
}
@@ -1487,9 +1490,11 @@ bool GetCurrentDrawAsDebugVertices(DrawEngineCommon *drawEngine, GECommand cmd,
const u32 vertTypeID = GetVertTypeID(gstate.vertType, gstate.getUVGenMode());
const bool throughMode = (vertTypeID & GE_VTYPE_THROUGH) != 0;
// Points is the only primitive that generates 6x as many vertices as input indices (2 triangles per point).
// Two expansions happen in here, and both multiply the input count, so a fixed 65536 wasn't
// enough: index generation turns strips/fans into up to 3 indices per input index, and then
// RunSoftwareTransform can expand points/lines/rects to 6 more each, written after those.
std::vector<u16> indexTemp;
indexTemp.resize(65536); // (prim == GEPrimitiveType::GE_PRIM_POINTS ? count * 6 : count * 3) * 4);
indexTemp.resize((size_t)count * 3 * 7 + 32);
// First, inspect the indices to find the range we need to decode.
const u8 *indsPtr = Memory::GetPointerUnchecked(gstate_c.indexAddr);
+4 -1
View File
@@ -1730,7 +1730,10 @@ void NativeAxis(const AxisInput *axes, size_t count) {
for (size_t i = 0; i < count; i++) {
const AxisInput &axis = axes[i];
HLEPlugins::PluginDataAxis[axis.axisId] = axis.value;
// axisId comes straight from the device, and can exceed the axes we know about.
if ((size_t)axis.axisId < JOYSTICK_AXIS_MAX) {
HLEPlugins::PluginDataAxis[axis.axisId] = axis.value;
}
}
}
+2 -5
View File
@@ -1,9 +1,6 @@
#Mon Aug 31 16:15:56 CEST 2026
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.1-bin.zip
networkTimeout=10000
retries=0
retryBackOffMs=500
validateDistributionUrl=true
distributionUrl=https\://services.gradle.org/distributions/gradle-9.7.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists