Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(chain): sanitize derivation index before apply changeset #1792

Open
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

f3r10
Copy link
Contributor

@f3r10 f3r10 commented Jan 7, 2025

fix: #1688

Description

Right now, ff a descriptor has hardened child steps when inserting a descriptor throws a panic. This PR adds a previous descriptor sanitization controlling such cases.

An additional problem pointed out by @ValuedMammal is that when the apply_changest is applied a malicious crafter index could be greater that BIP32_MAX_INDEX. This PR adds a verification to handle such cases.

Notes to the reviewers

Changelog notice

Checklists

All Submissions:

  • I've signed all my commits
  • I followed the contribution guidelines
  • I ran cargo fmt and cargo clippy before committing

@f3r10
Copy link
Contributor Author

f3r10 commented Jan 7, 2025

@notmandatory I am also working on this PR but is not been assigned to me yet.

@ValuedMammal
Copy link
Contributor

If this helps, it appears that insert_descriptor can cause a panic in miniscript if passed a descriptor with hardened steps.

#[test]
fn insert_descriptor_should_reject_hardened_steps() {
    use bdk_chain::keychain_txout::KeychainTxOutIndex;

    // This descriptor has hardened child steps
    let s = "wpkh([e273fe42/84h/1h/0h/0h]tpubDEBY7DLZ5kK6pMC2qdtVvKpe6CiAmVDx1SiLtLS3V4GAGyRvsuVbCYReJ9oQz1rfMapghJyUAYLqkqJ3Xadp3GSTCtdAFcKPgzAXC1hzz8a/*h)";
    let (desc, _) =
        <Descriptor<DescriptorPublicKey>>::parse_descriptor(&Secp256k1::new(), s).unwrap();

    // FIXME: This will panic by attempting to derive a script pubkey from a hardened step
    let mut indexer = KeychainTxOutIndex::<&str>::new(10);
    let _ = indexer.insert_descriptor("keychain", desc);
}

@f3r10
Copy link
Contributor Author

f3r10 commented Jan 8, 2025

it appears that insert_descriptor can cause a panic in miniscript if passed a descriptor with hardened steps.

yes thanks @ValuedMammal I did a similar test 😅 It panics because of desc.descriptor_id() 🤔

So I am going to add the sanitize part in the insert_descriptor function trying to handle that error. WDYT?

@ValuedMammal
Copy link
Contributor

I think it makes sense to handle the error in insert_descriptor, but also I thought about it more and I guess we could have a test showing that KeychainTxOutIndex::apply_changeset can handle a maliciously crafted changeset e.g. where the index is >BIP32_MAX_INDEX, although I'm fairly certain that SpkIterator always caps the range at this value.

@f3r10
Copy link
Contributor Author

f3r10 commented Jan 8, 2025

I think it makes sense to handle the error in insert_descriptor, but also I thought about it more and I guess we could have a test showing that KeychainTxOutIndex::apply_changeset can handle a maliciously crafted changeset e.g. where the index is >BIP32_MAX_INDEX, although I'm fairly certain that SpkIterator always caps the range at this value.

I tried to create such a test but it takes a lot of time to complete .. has been running for over 60 seconds 🤔

#[test]
fn insert_descriptor_maliciously() {
    use bdk_chain::keychain_txout::KeychainTxOutIndex;

    let s = "wpkh([e273fe42/84h/1h/0h/0h]tpubDEBY7DLZ5kK6pMC2qdtVvKpe6CiAmVDx1SiLtLS3V4GAGyRvsuVbCYReJ9oQz1rfMapghJyUAYLqkqJ3Xadp3GSTCtdAFcKPgzAXC1hzz8a/*)";
    let (desc, _) =
        <Descriptor<DescriptorPublicKey>>::parse_descriptor(&Secp256k1::new(), s).unwrap();

     let changeset: ChangeSet = ChangeSet {
        last_revealed: [(desc.descriptor_id(), bdk_chain::BIP32_MAX_INDEX + 1)].into(),
    };

    let mut indexer_a = KeychainTxOutIndex::<TestKeychain>::new(10);
    indexer_a
        .insert_descriptor(TestKeychain::External, desc.clone())
        .expect("must insert keychain");
    indexer_a
        .apply_changeset(changeset.clone())
        .expect("must apply changeset");

}

@f3r10
Copy link
Contributor Author

f3r10 commented Jan 8, 2025

@ValuedMammal just to be sure, the sanitize check has to be only made in insert_descriptor and not in KeychainTxOutIndex::apply_changeset, right?

Copy link
Member

@evanlinjin evanlinjin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approach NACK

The thing with changesets is that they are designed to be loaded from persistence and ideally be fully monotone in nature. Erroring when applying the changeset is against this design principle.

I think the correct solution would be to modify the ChangeSet::last_revealed values in KeychainTxOutIndex::apply_changeset to be maxed at 2^31-1.

Whether the descriptor has any hardened steps in the derivation path should be checked in insert_descriptor (which already returns an error).

@notmandatory notmandatory added the audit Suggested as result of external code audit label Jan 14, 2025
@f3r10 f3r10 force-pushed the sanitize_derivation_index_in_apply_changeset branch from 64a0976 to 2582e02 Compare January 16, 2025 21:42
@f3r10 f3r10 force-pushed the sanitize_derivation_index_in_apply_changeset branch from a3f087c to 6c14a7d Compare January 19, 2025 16:35
@f3r10 f3r10 marked this pull request as ready for review January 19, 2025 16:35
@ValuedMammal ValuedMammal added this to the 1.1.0 milestone Jan 21, 2025
Comment on lines +796 to +798
if index > BIP32_MAX_INDEX {
return Err(InsertDescriptorError::OutOfBounds);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔧 This comment #1792 (review) is saying, and I agree, that in principle apply_changeset should not have to return a Result.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
audit Suggested as result of external code audit
Projects
Status: Needs Review
Development

Successfully merging this pull request may close these issues.

'apply_changeset' the derivation index aren't sanitized
4 participants