Skip to content

Commit d445545

Browse files
committed
Merge bitcoin/bitcoin#28832: fuzz: rule-out too deep derivation paths in descriptor parsing targets
a44808f fuzz: rule-out too deep derivation paths in descriptor parsing targets (Antoine Poinsot) Pull request description: This fixes the `mocked_descriptor_parse` timeout reported in #28812 and direct the targets more toward what they are intended to fuzz: the descriptor syntax. ACKs for top commit: sipa: utACK a44808f achow101: ACK a44808f dergoegge: ACK a44808f - Not running into timeouts anymore TheCharlatan: ACK a44808f Tree-SHA512: a5dd1dbe9adf8f088bdc435addab88b56f435e6d7d2065bd6d5c6d80a32e3f1f97d3d2323131ab233618cd6dcc477c458abe3c4c865ab569449b8bc176231e93
2 parents 737e588 + a44808f commit d445545

File tree

4 files changed

+44
-0
lines changed

4 files changed

+44
-0
lines changed

src/test/fuzz/descriptor_parse.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,11 @@ void initialize_mocked_descriptor_parse()
6767

6868
FUZZ_TARGET(mocked_descriptor_parse, .init = initialize_mocked_descriptor_parse)
6969
{
70+
// Key derivation is expensive. Deriving deep derivation paths take a lot of compute and we'd
71+
// rather spend time elsewhere in this target, like on the actual descriptor syntax. So rule
72+
// out strings which could correspond to a descriptor containing a too large derivation path.
73+
if (HasDeepDerivPath(buffer)) return;
74+
7075
const std::string mocked_descriptor{buffer.begin(), buffer.end()};
7176
if (const auto descriptor = MOCKED_DESC_CONVERTER.GetDescriptor(mocked_descriptor)) {
7277
FlatSigningProvider signing_provider;
@@ -78,6 +83,9 @@ FUZZ_TARGET(mocked_descriptor_parse, .init = initialize_mocked_descriptor_parse)
7883

7984
FUZZ_TARGET(descriptor_parse, .init = initialize_descriptor_parse)
8085
{
86+
// See comment above for rationale.
87+
if (HasDeepDerivPath(buffer)) return;
88+
8189
const std::string descriptor(buffer.begin(), buffer.end());
8290
FlatSigningProvider signing_provider;
8391
std::string error;

src/test/fuzz/util/descriptor.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,3 +70,17 @@ std::optional<std::string> MockedDescriptorConverter::GetDescriptor(std::string_
7070

7171
return desc;
7272
}
73+
74+
bool HasDeepDerivPath(const FuzzBufferType& buff, const int max_depth)
75+
{
76+
auto depth{0};
77+
for (const auto& ch: buff) {
78+
if (ch == ',') {
79+
// A comma is always present between two key expressions, so we use that as a delimiter.
80+
depth = 0;
81+
} else if (ch == '/') {
82+
if (++depth > max_depth) return true;
83+
}
84+
}
85+
return false;
86+
}

src/test/fuzz/util/descriptor.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include <key_io.h>
99
#include <util/strencodings.h>
1010
#include <script/descriptor.h>
11+
#include <test/fuzz/fuzz.h>
1112

1213
#include <functional>
1314

@@ -45,4 +46,13 @@ class MockedDescriptorConverter {
4546
std::optional<std::string> GetDescriptor(std::string_view mocked_desc) const;
4647
};
4748

49+
//! Default maximum number of derivation indexes in a single derivation path when limiting its depth.
50+
constexpr int MAX_DEPTH{2};
51+
52+
/**
53+
* Whether the buffer, if it represents a valid descriptor, contains a derivation path deeper than
54+
* a given maximum depth. Note this may also be hit for deriv paths in origins.
55+
*/
56+
bool HasDeepDerivPath(const FuzzBufferType& buff, const int max_depth = MAX_DEPTH);
57+
4858
#endif // BITCOIN_TEST_FUZZ_UTIL_DESCRIPTOR_H

src/wallet/test/fuzz/scriptpubkeyman.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,21 @@ void initialize_spkm()
4949
MOCKED_DESC_CONVERTER.Init();
5050
}
5151

52+
/**
53+
* Key derivation is expensive. Deriving deep derivation paths take a lot of compute and we'd rather spend time
54+
* elsewhere in this target, like on actually fuzzing the DescriptorScriptPubKeyMan. So rule out strings which could
55+
* correspond to a descriptor containing a too large derivation path.
56+
*/
57+
static bool TooDeepDerivPath(std::string_view desc)
58+
{
59+
const FuzzBufferType desc_buf{reinterpret_cast<const unsigned char *>(desc.data()), desc.size()};
60+
return HasDeepDerivPath(desc_buf);
61+
}
62+
5263
static std::optional<std::pair<WalletDescriptor, FlatSigningProvider>> CreateWalletDescriptor(FuzzedDataProvider& fuzzed_data_provider)
5364
{
5465
const std::string mocked_descriptor{fuzzed_data_provider.ConsumeRandomLengthString()};
66+
if (TooDeepDerivPath(mocked_descriptor)) return {};
5567
const auto desc_str{MOCKED_DESC_CONVERTER.GetDescriptor(mocked_descriptor)};
5668
if (!desc_str.has_value()) return std::nullopt;
5769

0 commit comments

Comments
 (0)