guest_cycle_scale: range-check TOML integers before narrowing

Reject negative and >32-bit addr/value/size/mask in guest_cycle_scale_gate
and out-of-range guest_cycle_scale instead of wrapping or clamping; add
regression cases. Regenerate the OpenBIOS emitter stamp.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Shokunin
2026-10-07 21:49:37 -07:00
parent 91cd6db705
commit cba31b1923
3 changed files with 43 additions and 10 deletions
+1 -1
View File
@@ -1 +1 @@
69641913e358f4c3128a93dadf5824c7929afeaf8df6c5b9a6cc645c0f43d0f5
01cfc201a7c71e2555e5828c67aeaa29f295bf4ebfb4e182a3c606e2a4812857
+19 -9
View File
@@ -618,16 +618,16 @@ static RuntimeConfig parse_runtime_block(const toml::value& cfg, const fs::path&
// guest clock a fixed cost per instruction, and scale N charges 1/N of
// it, so N times as much game code fits between two VBlanks while
// VBlank, timers, CD, SPU and DMA keep hardware time. 1 = faithful;
// clamped to 1..64. guest_cycle_scale_gated: the scale applies only
// values outside 1..64 are rejected. guest_cycle_scale_gated: the scale applies only
// while the mod gate is open (psx_mod_set_guest_cycle_scale_gate); see
// guest_cycle_scale_gate below for the declarative RAM gate.
if (cfg.contains("timing")) {
const toml::value& tm = toml::find(cfg, "timing");
if (tm.contains("guest_cycle_scale")) {
long long n = toml::find<long long>(tm, "guest_cycle_scale");
if (n < 1) n = 1;
if (n > 64) n = 64;
rt.guest_cycle_scale = (int)n;
const long long n = toml::find<long long>(tm, "guest_cycle_scale");
if (n < 1 || n > 64)
throw std::runtime_error("[timing] guest_cycle_scale must be 1..64");
rt.guest_cycle_scale = static_cast<int>(n);
}
if (tm.contains("guest_cycle_scale_gated"))
rt.guest_cycle_scale_gated = toml::find<bool>(tm, "guest_cycle_scale_gated");
@@ -640,12 +640,22 @@ static RuntimeConfig parse_runtime_block(const toml::value& cfg, const fs::path&
else preds.push_back(g);
if (preds.size() > 8)
throw std::runtime_error("[timing] guest_cycle_scale_gate: at most 8 predicates");
// Range-check the signed TOML integer before narrowing, so an
// oversized or negative value cannot wrap into a valid one.
auto u32_field = [](const toml::value& pv, const char* key) -> uint32_t {
const long long v = toml::find<long long>(pv, key);
if (v < 0 || v > 0xFFFFFFFFLL)
throw std::runtime_error(
std::string("[timing] guest_cycle_scale_gate: ") + key +
" must be 0..0xFFFFFFFF");
return static_cast<uint32_t>(v);
};
for (const toml::value& pv : preds) {
RuntimeConfig::GuestCycleScaleGatePred p;
p.addr = (uint32_t)toml::find<long long>(pv, "addr");
p.value = (uint32_t)toml::find<long long>(pv, "value");
if (pv.contains("size")) p.size = (uint32_t)toml::find<long long>(pv, "size");
if (pv.contains("mask")) p.mask = (uint32_t)toml::find<long long>(pv, "mask");
p.addr = u32_field(pv, "addr");
p.value = u32_field(pv, "value");
if (pv.contains("size")) p.size = u32_field(pv, "size");
if (pv.contains("mask")) p.mask = u32_field(pv, "mask");
const uint32_t phys = p.addr & 0x1FFFFFFFu;
if ((p.size != 1 && p.size != 2 && p.size != 4) ||
(phys & (p.size - 1u)) != 0 || phys >= 0x00800000u)
@@ -499,6 +499,29 @@ static void test_timing_gate() {
try { (void)PSXRecompV4::load_game_config(p); } catch (const std::exception&) { rejected = true; }
check(rejected, "timing: non-RAM gate address rejected");
fs::remove(p);
/* Oversized and negative values must be rejected, not wrapped to 32 bits. */
const struct { const char* body; const char* what; } bad[] = {
{ "guest_cycle_scale_gate = { addr = 0x100010000, value = 1 }", "oversized addr" },
{ "guest_cycle_scale_gate = { addr = -4, value = 1 }", "negative addr" },
{ "guest_cycle_scale_gate = { addr = 0x80010000, value = 0x100000001 }", "oversized value" },
{ "guest_cycle_scale_gate = { addr = 0x80010000, value = -1 }", "negative value" },
{ "guest_cycle_scale_gate = { addr = 0x80010000, value = 1, size = 0x100000004 }", "oversized size" },
{ "guest_cycle_scale_gate = { addr = 0x80010000, value = 1, size = -4 }", "negative size" },
{ "guest_cycle_scale_gate = { addr = 0x80010000, value = 1, mask = 0x1FFFFFFFF }", "oversized mask" },
{ "guest_cycle_scale_gate = { addr = 0x80010000, value = 1, mask = -1 }", "negative mask" },
{ "guest_cycle_scale = 0", "scale 0" },
{ "guest_cycle_scale = 65", "scale 65" },
{ "guest_cycle_scale = -8", "negative scale" },
{ "guest_cycle_scale = 0x100000008", "oversized scale" },
};
for (const auto& b : bad) {
p = write_game_toml("ves_timing_range.toml", std::string("[timing]\n") + b.body + "\n");
rejected = false;
try { (void)PSXRecompV4::load_game_config(p); } catch (const std::exception&) { rejected = true; }
check(rejected, (std::string("timing: rejected ") + b.what).c_str());
fs::remove(p);
}
}
int main() {