#40806 [BC-High] Users can submit deposits containing large `reclaim_scripts` to DoS Emily and Signers
Description
Brief/Intro
Vulnerability Details
/// Parse the reclaim script for the lock time.
///
/// The goal of this function is to make sure that there are no
/// surprises in the reclaim script. These scripts are conceptually
/// very simple and are their format is
/// ```text
/// <locked-time> OP_CHECKSEQUENCEVERIFY <rest-of-reclaim-script>
/// ```
/// This function extracts the <locked-time> from the script. If the
/// script does not start with <locked-time> OP_CHECKSEQUENCEVERIFY
/// then we return an error.
///
/// See https://github.com/stacks-network/sbtc/issues/30 for the
/// expected format of the reclaim script. And see BIP-0112 for
/// the details and input conditions of OP_CHECKSEQUENCEVERIFY:
/// https://github.com/bitcoin/bips/blob/812907c2b00b92ee31e2b638622a4fe14a428aee/bip-0112.mediawiki#summary
pub fn parse(reclaim_script: &ScriptBuf) -> Result<Self, Error> {
let (lock_time, script) = match reclaim_script.as_bytes() {
// These first two branches check for the case when the script
// is written with as few bytes as possible (called minimal
// CScriptNum format or something like that).
[0, OP_CSV, script @ ..] => (0, script),
// This catches numbers 1-16 and -1. Negative numbers are
// invalid for OP_CHECKSEQUENCEVERIFY, but we filter them out
// later in `ReclaimScriptInputs::try_new`.
[n, OP_CSV, script @ ..]
if OP_PUSHNUM_NEG1 == *n || (OP_PUSHNUM_1..=OP_PUSHNUM_16).contains(n) =>
{
(*n as i64 - OP_PUSHNUM_1 as i64 + 1, script)
}
// Numbers in bitcoin script are typically only 4 bytes (with a
// range from -2**31+1 to 2**31-1), unless we are working with
// OP_CSV or OP_CLTV, where 5-byte numbers are acceptable (with
// a range of 0 to 2**39-1). See the following for how the code
// works in bitcoin-core:
// https://github.com/bitcoin/bitcoin/blob/v27.1/src/script/interpreter.cpp#L531-L573
// That said, we only accepts 4-byte unsigned integers, and we
// check that below.
[n, rest @ ..] if *n <= 5 && rest.get(*n as usize) == Some(&OP_CSV) => {
// We know the error and panic paths cannot happen because
// of the above `if` check.
let (script_num, [OP_CSV, script @ ..]) = rest.split_at(*n as usize) else {
return Err(Error::InvalidReclaimScript);
};
(read_scriptint(script_num, 5)?, script)
}
_ => return Err(Error::InvalidReclaimScript),
};
let lock_time =
u32::try_from(lock_time).map_err(|_| Error::InvalidReclaimScriptLockTime(lock_time))?;
let script = ScriptBuf::from_bytes(script.to_vec());
ReclaimScriptInputs::try_new(lock_time, script)
}Impact Details
References
Proof of Concept
Proof of Concept
Previous#41014 [BC-Low] The signer can submit multi-tx first to make the coordinator's submission failNext#41111 [BC-Medium] A malicious signer could manipulate withdrawal decisions preventing accepted and rejected withdrawals from getting confirmed on Stacks chain
Was this helpful?