rpc/net: gate and harden stratummine, keep regtest off the live network, init nSPV filter
stratummine (rpc/mining.cpp), a test-only reference miner, was registered
unconditionally behind a bare #ifndef WIN32 with okSafeMode=true, so it shipped
as a live RPC on every non-Windows release build. Four fixes:
- Gated behind an explicit, default-off -stratummine, with regtest exempt so
qa/rpc-tests can still drive it. Deliberately NOT gated on fExperimentalMode:
that defaults to TRUE (init.cpp:1195), so such a gate is a no-op -- an error
made and caught while testing this change.
- mining.notify's three hash fields went straight into uint256(ParseHex(...)).
uint256's vector ctor asserts on a wrong size (uint256.cpp:30) and NDEBUG is
defined nowhere in this build, so that assert is live in release; ParseHex
also truncates silently at the first non-hex character. A short or garbled
field therefore ABORTED THE DAEMON. Added StratumHex256(), which requires 64
hex chars and a 32-byte result, and all three fields are validated before any
is committed so a bad job is rejected rather than half-applied.
- processLine ran inside the window where the RandomX cache, the VM and the
socket are live, all released on the normal path only, and every get_str()
throws on a type mismatch -- so a malformed message leaked 256 MB and the fd.
Body wrapped in try/catch: ignore the line, keep mining.
- Caller-supplied timeout clamped to [1, 3600]; it was unbounded, pinning an
RPC worker and the cache indefinitely. okSafeMode -> false.
Verified against a hostile stratum server on regtest:
- 4 malformed mining.notify payloads (short hex, non-hex, wrong JSON type,
31 bytes) -> all rejected, daemon alive, 0 assertions. The 31-byte case is
the one that previously hit the uint256 assert.
- valid job first (so RandomX actually allocates) then garbage mid-mine ->
RSS delta +2.2 MB, i.e. cache and VM released, not the ~256 MB a leak leaves.
- regtest exemption confirmed: stratummine runs past the gate there.
The non-regtest refusal path is by code reading only -- a testnet node on this
host collides with the production daemon's RPC port, so it was not exercised.
hush_utils.h: stop injecting the mainnet node1-node10.dragonx.is addnode seeds
when -regtest or -testnet is set. regtest reuses mainnet's network magic, so a
supposedly isolated node was handshaking production peers and pulling their
headers into its own index. Gated at the injection site only -- isdragonx itself
must stay true, because it also selects ac_private, ac_algo, blocktime and the
reward/halving schedule (an earlier version of this patch gated isdragonx itself
and silently turned ac_private off on regtest). hush_args() runs between
ParseParameters() and ReadConfigFile() (bitcoind.cpp:115/144/158), so this sees a
command-line -regtest, as qa/rpc-tests uses, but not a config-file regtest=1.
hush_nSPV_fullnode.h: initialize `filter` at both sites. It was assigned only on
the len-11 request form; the other two passed uninitialized stack memory to
NSPV_getaddressutxos/NSPV_getaddresstxids, remotely reachable since
HUSH_NSPV_FULLNODE is on by default.
getblocktemplate_proposals.py still passes.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FU87LdsJZiZkfq1eXubpeo
This commit is contained in:
@@ -660,7 +660,7 @@ void hush_nSPVreq(CNode *pfrom,std::vector<uint8_t> request) // received a reque
|
||||
struct NSPV_utxosresp U;
|
||||
if ( len >= 2 && len < 64+5 && request[1] < 64 && (request[1] == len-3 || request[1] == len-7 || request[1] == len-11) )
|
||||
{
|
||||
int32_t skipcount = 0; char coinaddr[64]; uint32_t filter; uint8_t isCC = 0;
|
||||
int32_t skipcount = 0; char coinaddr[64]; uint32_t filter = 0; uint8_t isCC = 0; // only assigned on the len-11 form; the other two passed it on uninitialized
|
||||
memcpy(coinaddr,&request[2],request[1]); // request[1] < 64 bounds the copy + the terminator write
|
||||
coinaddr[request[1]] = 0;
|
||||
if ( request[1] == len-3 )
|
||||
@@ -698,7 +698,7 @@ void hush_nSPVreq(CNode *pfrom,std::vector<uint8_t> request) // received a reque
|
||||
struct NSPV_txidsresp T;
|
||||
if ( len >= 2 && len < 64+5 && request[1] < 64 && (request[1] == len-3 || request[1] == len-7 || request[1] == len-11) )
|
||||
{
|
||||
int32_t skipcount = 0; char coinaddr[64]; uint32_t filter; uint8_t isCC = 0;
|
||||
int32_t skipcount = 0; char coinaddr[64]; uint32_t filter = 0; uint8_t isCC = 0; // only assigned on the len-11 form; the other two passed it on uninitialized
|
||||
memcpy(coinaddr,&request[2],request[1]); // request[1] < 64 bounds the copy + the terminator write
|
||||
coinaddr[request[1]] = 0;
|
||||
if ( request[1] == len-3 )
|
||||
|
||||
Reference in New Issue
Block a user