From fc06a43dd74452da35a6e930ab10424eb574a844 Mon Sep 17 00:00:00 2001 From: DanS Date: Tue, 14 Jul 2026 11:21:28 -0500 Subject: [PATCH] fix(consensus): bound OP_RETURN opretlen + clamp notary pubkeys array Defensive-audit findings (adversarially verified + fleet stability-tested): #4 (CRITICAL) hush_voutupdate trusted an attacker-decoded OP_RETURN length (opretlen, up to 65535 via OP_PUSHDATA2) with no check against the real script length, driving up to ~64KB out-of-bounds reads through hush_stateupdate -> hush_eventadd_opreturn -> hush_kvupdate (persisted to disk, leaked via kvsearch RPC, reliable crash on block connect). Reject any opret claiming more bytes than remain in the script, at the single taint source. #5 (HIGH) notary-ratification loop did memcpy(pubkeys[numvalid++],..) into a fixed uint8_t[64][33] with no bound; >64 crafted vouts smashed the stack. Clamp numvalid < 64. Co-Authored-By: Claude Opus 4.8 --- src/hush.h | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/hush.h b/src/hush.h index 4863547d7..fad5be098 100644 --- a/src/hush.h +++ b/src/hush.h @@ -591,6 +591,14 @@ int32_t hush_voutupdate(bool fJustCheck,int32_t *isratificationp,int32_t notaryi opretlen += (scriptbuf[len++] << 8); } opoffset = len; + // SECURITY (Finding #4): opretlen is attacker-controlled (up to 65535 via OP_PUSHDATA2) + // and was previously used with no bounds check. scriptbuf is a fixed DRAGON_MAXSCRIPTSIZE + // stack buffer in hush_connectblock, so an oversized opretlen drives out-of-bounds reads in + // the downstream 'K'/KV and notarization paths (persisted to disk, leaked via kvsearch RPC, + // reliable crash on block connect). Reject any opret claiming more bytes than actually + // remain in the real script; this mirrors the no-OP_RETURN fall-through so nothing valid changes. + if ( opretlen < 0 || opretlen > scriptlen - len ) + return(notaryid); matched = 0; if ( SMART_CHAIN_SYMBOL[0] == 0 ) { @@ -933,7 +941,7 @@ int32_t hush_connectblock(bool fJustCheck, CBlockIndex *pindex,CBlock& block) if ( len >= sizeof(uint32_t) && len <= sizeof(scriptbuf) ) { memcpy(scriptbuf,(uint8_t *)&block.vtx[i].vout[j].scriptPubKey[0],len); - if ( len == 35 && scriptbuf[0] == 33 && scriptbuf[34] == 0xac ) + if ( len == 35 && scriptbuf[0] == 33 && scriptbuf[34] == 0xac && numvalid < (int32_t)(sizeof(pubkeys)/sizeof(pubkeys[0])) ) { memcpy(pubkeys[numvalid++],scriptbuf+1,33); for (k=0; k<33; k++)