fix(q-w0): six audit fix-nows — linked-lag stereo SOLA, playable-span prime bound, declick dead-state, provenance cursor hardening, rate-derived gain ramp + fade ceiling
This commit is contained in:
+34
-11
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <limits>
|
||||
|
||||
// provenance implementation — pure, self-contained (no third-party lib, mirror of
|
||||
// bank_model's hand-rolled encoding discipline).
|
||||
@@ -60,22 +61,33 @@ public:
|
||||
bool ok() const { return ok_; }
|
||||
bool atEnd() const { return pos_ >= s_.size(); }
|
||||
|
||||
// Reads one length-prefixed field into `out`. Fails on a missing ':',
|
||||
// non-numeric length, or a length that runs past the end.
|
||||
// Reads one length-prefixed field into `out`. Fails on a missing ':', an empty or
|
||||
// non-numeric length, a length that overflows SIZE_MAX, or a length that runs past
|
||||
// the end. Hardened form backported from the assignment_request / sample_usage
|
||||
// siblings (Q-W0 T2-01a): the digit count is capped at 20 (the decimal width of
|
||||
// SIZE_MAX on a 64-bit host) so a crafted 200-digit length cannot accumulate past
|
||||
// SIZE_MAX via repeated multiply, and the bounds check is subtraction-first so a
|
||||
// huge `len` cannot wrap `start + len` past the end test.
|
||||
bool field(std::string& out) {
|
||||
if (!ok_) return false;
|
||||
std::size_t colon = s_.find(':', pos_);
|
||||
const std::size_t colon = s_.find(':', pos_);
|
||||
if (colon == std::string::npos) return fail();
|
||||
// Parse the length digits [pos_, colon).
|
||||
std::size_t len = 0;
|
||||
if (colon == pos_) return fail(); // empty length token
|
||||
// Cap: SIZE_MAX fits in at most 20 decimal digits; a longer run is bogus.
|
||||
if (colon - pos_ > 20u) return fail();
|
||||
std::size_t len = 0;
|
||||
for (std::size_t i = pos_; i < colon; ++i) {
|
||||
char c = s_[i];
|
||||
const char c = s_[i];
|
||||
if (c < '0' || c > '9') return fail();
|
||||
len = len * 10 + static_cast<std::size_t>(c - '0');
|
||||
const std::size_t digit = static_cast<std::size_t>(c - '0');
|
||||
// Overflow guard: if len would exceed SIZE_MAX after multiply+add, fail.
|
||||
if (len > (std::numeric_limits<std::size_t>::max() - digit) / 10u)
|
||||
return fail();
|
||||
len = len * 10u + digit;
|
||||
}
|
||||
const std::size_t start = colon + 1;
|
||||
if (start + len > s_.size()) return fail();
|
||||
// Subtraction-first form: start + len cannot wrap on a huge len.
|
||||
if (start > s_.size() || len > s_.size() - start) return fail();
|
||||
out.assign(s_, start, len);
|
||||
pos_ = start + len;
|
||||
return true;
|
||||
@@ -87,14 +99,20 @@ public:
|
||||
return toInt(f, out);
|
||||
}
|
||||
|
||||
// A length-prefixed unsigned decimal (the GUID count). Hardened (Q-W0 T2-01a, the
|
||||
// sample_usage fieldCount pattern): fails on empty, non-digit, a digit run past 20
|
||||
// (SIZE_MAX's decimal width), or an accumulate that would overflow SIZE_MAX.
|
||||
bool fieldSizeT(std::size_t& out) {
|
||||
std::string f;
|
||||
if (!field(f)) return false;
|
||||
if (f.empty()) return fail();
|
||||
if (f.empty() || f.size() > 20u) return fail();
|
||||
std::size_t v = 0;
|
||||
for (char c : f) {
|
||||
for (const char c : f) {
|
||||
if (c < '0' || c > '9') return fail();
|
||||
v = v * 10 + static_cast<std::size_t>(c - '0');
|
||||
const std::size_t digit = static_cast<std::size_t>(c - '0');
|
||||
if (v > (std::numeric_limits<std::size_t>::max() - digit) / 10u)
|
||||
return fail();
|
||||
v = v * 10u + digit;
|
||||
}
|
||||
out = v;
|
||||
return true;
|
||||
@@ -196,6 +214,11 @@ std::optional<CaptureRecipe> parseFingerprint(const std::string& fingerprint) {
|
||||
|
||||
std::size_t guidCount = 0;
|
||||
if (!c.fieldSizeT(guidCount)) return std::nullopt;
|
||||
// Q-W0 T2-01a (the sample_usage count-sanity pattern): each GUID field costs at least
|
||||
// 2 wire bytes ("0:"), so a count past size/2 is provably bogus — reject BEFORE the
|
||||
// reserve, so a corrupt/crafted persisted fingerprint can never drive reserve(huge)
|
||||
// into std::length_error / bad_alloc through the shell.
|
||||
if (guidCount > fingerprint.size() / 2u + 1u) return std::nullopt;
|
||||
r.trackGuids.reserve(guidCount);
|
||||
for (std::size_t i = 0; i < guidCount; ++i) {
|
||||
std::string g;
|
||||
|
||||
Reference in New Issue
Block a user