stratum: do not abort the daemon on a malformed 63-character job_id

The "EWBF 31 bytes job_id fix" in stratum_mining_submit completes a
63-character job_id with each hex digit in turn and feeds the result
straight to uint256(). ParseHex() stops at the first non-hex character
and returns a shorter vector without signalling an error, and
base_blob(const std::vector<unsigned char>&) asserts vch.size() == 32.

So a single mining.submit whose job_id is any 63-character string
containing a non-hex byte -- 63 spaces will do -- aborts the node.
asserts are live in release builds here: -DNDEBUG appears only in
leveldb's own makefile, never in configure.ac, and the shipped binary
still carries the assertion string. The dispatch loop catches UniValue
and std::exception; abort() goes through both.

Size-check each candidate before constructing, as ParseUInt256() a few
hundred lines up already does for the ordinary path. If none of the 16
completions parse, ret stays null, misses work_templates, and the
handler returns false exactly as it does for any unknown job.

Not gated on IsHex(job_id_str): 63 is odd and IsHex() requires an even
length, so that test would disable the EWBF path this code exists for.
This commit is contained in:
2026-08-28 23:30:24 -05:00
parent d05302d450
commit b3e81f1eda

View File

@@ -1334,7 +1334,16 @@ UniValue stratum_mining_submit(StratumClient& client, const UniValue& params)
if (job_id_str.length() == 63) { if (job_id_str.length() == 63) {
fEWBFJobIDFixNeeded = true; fEWBFJobIDFixNeeded = true;
for(const auto& hexDigit : hexDigits) { for(const auto& hexDigit : hexDigits) {
ret = uint256(ParseHex(job_id_str + hexDigit)); // ParseHex() stops at the first non-hex character and returns a SHORT vector
// without signalling an error, and base_blob(const std::vector<unsigned char>&)
// asserts vch.size() == 32. Constructing without checking therefore lets any
// 63-character job_id containing a non-hex byte abort the daemon -- from an
// unauthenticated client, before any other validation. Skip bad candidates
// instead; if none of the 16 completions parse, ret stays null, misses
// work_templates below, and the handler returns false cleanly.
std::vector<unsigned char> vch = ParseHex(job_id_str + hexDigit);
if (vch.size() != 32) continue;
ret = uint256(vch);
if (work_templates.count(ret)) break; if (work_templates.count(ret)) break;
} }
} }