From 0714b48664e3705eddb1c9b5df18fc23bc7a51d7 Mon Sep 17 00:00:00 2001 From: giaki3003 Date: Sat, 4 Jul 2026 13:40:36 +0200 Subject: [PATCH 1/2] fix: P2P: block merkle roots not validated before inserting into archive DB Bug: s2-r22 (primary) Finding: https://giaki3003.tech/#/findings/20260603-2200-thunder-peer-body-poison-archive-and-net-task-halt Severity: Medium Co-Authored-By: Claude Opus 4.8 (1M context) --- .../ses_0d31354b3ffe3FPJ6zqmrkH2ni.json | 10 +++++ lib/archive.rs | 30 ++++++++++++++ lib/node/net_task.rs | 40 ++++++++++++++++--- 3 files changed, 75 insertions(+), 5 deletions(-) create mode 100644 .omo/run-continuation/ses_0d31354b3ffe3FPJ6zqmrkH2ni.json diff --git a/.omo/run-continuation/ses_0d31354b3ffe3FPJ6zqmrkH2ni.json b/.omo/run-continuation/ses_0d31354b3ffe3FPJ6zqmrkH2ni.json new file mode 100644 index 0000000..cf17017 --- /dev/null +++ b/.omo/run-continuation/ses_0d31354b3ffe3FPJ6zqmrkH2ni.json @@ -0,0 +1,10 @@ +{ + "sessionID": "ses_0d31354b3ffe3FPJ6zqmrkH2ni", + "updatedAt": "2026-07-04T11:40:36.457Z", + "sources": { + "background-task": { + "state": "idle", + "updatedAt": "2026-07-04T11:40:36.457Z" + } + } +} \ No newline at end of file diff --git a/lib/archive.rs b/lib/archive.rs index 133006c..2ea3f0e 100644 --- a/lib/archive.rs +++ b/lib/archive.rs @@ -702,6 +702,36 @@ impl Archive { }) } + /// Delete a stored body, reversing the effects of [`Self::put_body`]. + /// + /// Used to discard a body that was stored before its contents could be + /// validated (e.g. a body received from a peer) and later turned out to be + /// invalid, so that the block is reported missing again by + /// [`Self::iter_missing_bodies`] and the real body is re-requested. + pub fn delete_body( + &self, + rwtxn: &mut RwTxn, + block_hash: BlockHash, + body: &Body, + ) -> Result<(), Error> { + self.bodies + .delete(rwtxn, &block_hash) + .map_err(DbError::from)?; + body.transactions.iter().try_for_each(|tx| { + let txid = tx.txid(); + let mut inclusions = self.get_tx_inclusions(rwtxn, txid)?; + inclusions.remove(&block_hash); + if inclusions.is_empty() { + self.txid_to_inclusions + .delete(rwtxn, &txid) + .map_err(DbError::from)?; + } else { + self.txid_to_inclusions.put(rwtxn, &txid, &inclusions)?; + } + Ok(()) + }) + } + /// Store a header. /// /// The following predicates MUST be met before calling this function: diff --git a/lib/node/net_task.rs b/lib/node/net_task.rs index 2573e27..05b4e4f 100644 --- a/lib/node/net_task.rs +++ b/lib/node/net_task.rs @@ -390,7 +390,7 @@ fn reorg_to_tip( } two_way_peg_data }; - let () = connect_tip_( + if let Err(err) = connect_tip_( &mut rwtxn, archive, mempool, @@ -398,7 +398,18 @@ fn reorg_to_tip( header, body, &two_way_peg_data, - )?; + ) { + // The stored body for this block failed validation (e.g. a peer + // supplied a body whose contents do not match the header's merkle + // root). Abort the reorg and discard the invalid body from the + // archive so that the block is reported missing again and the real + // body is re-requested, instead of the archive staying poisoned. + drop(rwtxn); + let mut rwtxn = env.write_txn().map_err(EnvError::from)?; + let () = archive.delete_body(&mut rwtxn, header.hash(), &body)?; + rwtxn.commit().map_err(RwTxnError::from)?; + return Err(err); + } let new_tip_hash = state.try_get_tip(&rwtxn)?.unwrap(); let bmm_verification = archive.get_best_main_verification(&rwtxn, new_tip_hash)?; @@ -1022,8 +1033,8 @@ impl NetTask { } } } - MailboxItem::NewTipReady(new_tip, _addr, resp_tx) => { - let reorg_applied = task::block_in_place(|| { + MailboxItem::NewTipReady(new_tip, addr, resp_tx) => { + let reorg_result = task::block_in_place(|| { reorg_to_tip( &self.ctxt.env, &self.ctxt.archive, @@ -1033,7 +1044,26 @@ impl NetTask { &self.ctxt.zmq_pub_handler, new_tip, ) - })?; + }); + // A tip supplied by a peer may carry a body that fails + // validation. Do not let that error propagate out of `run` + // and halt the whole net task: log it and drop the + // offending peer instead. `reorg_to_tip` has already + // discarded the invalid body so it will be re-requested. + let reorg_applied = match reorg_result { + Ok(reorg_applied) => reorg_applied, + Err(err) => { + tracing::warn!( + ?addr, + %err, + "Failed to reorg to new tip; dropping peer" + ); + if let Some(addr) = addr { + let () = self.ctxt.net.remove_active_peer(addr); + } + false + } + }; if let Some(resp_tx) = resp_tx { let () = resp_tx .send(reorg_applied) From a30437b9e29a406a6e3995087ea5de21573b2ae4 Mon Sep 17 00:00:00 2001 From: giaki3003 Date: Sat, 4 Jul 2026 14:14:44 +0200 Subject: [PATCH 2/2] fix: P2P: block merkle roots not validated before inserting into archive DB Bug: s2-r22 (port) Finding: https://giaki3003.tech/#/findings/20260603-2200-thunder-peer-body-poison-archive-and-net-task-halt Severity: Medium Co-Authored-By: Claude Opus 4.8 (1M context) --- .../ses_0d31354b3ffe3FPJ6zqmrkH2ni.json | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 .omo/run-continuation/ses_0d31354b3ffe3FPJ6zqmrkH2ni.json diff --git a/.omo/run-continuation/ses_0d31354b3ffe3FPJ6zqmrkH2ni.json b/.omo/run-continuation/ses_0d31354b3ffe3FPJ6zqmrkH2ni.json deleted file mode 100644 index cf17017..0000000 --- a/.omo/run-continuation/ses_0d31354b3ffe3FPJ6zqmrkH2ni.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "sessionID": "ses_0d31354b3ffe3FPJ6zqmrkH2ni", - "updatedAt": "2026-07-04T11:40:36.457Z", - "sources": { - "background-task": { - "state": "idle", - "updatedAt": "2026-07-04T11:40:36.457Z" - } - } -} \ No newline at end of file