Merge bitcoin/bitcoin#35440: wallet: check descriptor cache xpub length before decoding
What changed, and why it matters
This update fixes a wallet database loading bug where a damaged or tampered Bitcoin wallet file could cause the program to read past the end of a stored extended public key (xpub). The patch makes the loader check the stored xpub length before decoding it, and also replaces the old fixed-size buffer encoding with safer stream-based serialization used throughout the rest of the code. It is primarily a hardening fix against corrupt wallet data rather than a remote attack vector.
Treat this as a wallet data-integrity hardening fix. Users should upgrade to a release containing this patch, especially if they rely on descriptor wallets, and avoid opening wallet files from untrusted sources. Developers should verify that any custom tools writing descriptor cache records use the exact BIP32_EXTKEY_SIZE serialization.
Security signals we found
Out-of-bounds read in wallet descriptor cache deserialization
ASan container-overflow triggered by malformed on-disk record
Missing length validation between record size prefix and fixed-size decoder
Refactor of manual buffer Encode/Decode to stream-based Serialize/Unserialize
Unit tests added for short xpub cache records and invalid deserialization inputs
Evidence from the diff
The descriptor cache records (WALLETDESCRIPTORCACHE and WALLETDESCRIPTORLHCACHE) store serialized CExtPubKey values. Previously the loader read a vector whose size came from the record’s CompactSize prefix, then called CExtPubKey::Decode, which always consumed exactly BIP32_EXTKEY_SIZE (74) bytes. A record with a shorter vector caused an out-of-bounds read (container-overflow under ASan). The first commit adds an explicit length check returning DBErrors::CORRUPT for non-exact sizes. The second commit refactors CExtKey/CExtPubKey serialization to use Serialize/Unserialize through the standard stream framework, eliminating caller-supplied fixed buffers and routing base58, PSBT, and wallet cache code through the new methods while preserving byte compatibility.
Changed components
src/wallet/walletdb.cppsrc/key.hsrc/key.cppsrc/pubkey.hsrc/pubkey.cppsrc/key_io.cppsrc/psbt.hsrc/rpc/rawtransaction.cppInspect captured patch +225 / −85
### src/key.cpp
@@ -401,25 +401,6 @@ CExtPubKey CExtKey::Neuter() const {
return ret;
}
-void CExtKey::Encode(unsigned char code[BIP32_EXTKEY_SIZE]) const {
- code[0] = nDepth;
- std::ranges::copy(fingerprint, code+1);
- WriteBE32(code+5, nChild);
- memcpy(code+9, chaincode.begin(), 32);
- code[41] = 0;
- assert(key.size() == 32);
- memcpy(code+42, key.begin(), 32);
-}
-
-void CExtKey::Decode(const unsigned char code[BIP32_EXTKEY_SIZE]) {
- nDepth = code[0];
- std::copy_n(code + 1, fingerprint.size(), fingerprint.begin());
- nChild = ReadBE32(code+5);
- memcpy(chaincode.begin(), code+9, 32);
- key.Set(code+42, code+BIP32_EXTKEY_SIZE, true);
- if ((nDepth == 0 && (nChild != 0 || ReadLE32(fingerprint.data()) != 0)) || code[41] != 0) key = CKey();
-}
-
KeyPair::KeyPair(const CKey& key, const uint256* merkle_root)
{
static_assert(std::tuple_size<KeyType>() == sizeof(secp256k1_keypair));
### src/key.h
@@ -11,9 +11,13 @@
#include <script/keyorigin.h>
#include <serialize.h>
#include <support/allocators/secure.h>
+#include <support/cleanse.h>
#include <uint256.h>
+#include <array>
+#include <cassert>
#include <optional>
+#include <span>
#include <stdexcept>
#include <utility>
#include <vector>
@@ -253,8 +257,22 @@ struct CExtKey {
return key.GetPubKey().GetID().fingerprint();
}
- void Encode(unsigned char code[BIP32_EXTKEY_SIZE]) const;
- void Decode(const unsigned char code[BIP32_EXTKEY_SIZE]);
+ //! BIP32 serialization without the version bytes (BIP32_EXTKEY_SIZE bytes)
+ template <typename Stream>
+ void Serialize(Stream& s) const
+ {
+ assert(key.size() == 32);
+ s << nDepth << fingerprint << Using<BigEndianFormatter<4>>(nChild) << chaincode << uint8_t{0} << std::span{key.data(), key.size()};
+ }
+ template <typename Stream>
+ void Unserialize(Stream& s)
+ {
+ uint8_t key_prefix;
+ std::vector<unsigned char, secure_allocator<unsigned char>> ser_key(32);
+ s >> nDepth >> fingerprint >> Using<BigEndianFormatter<4>>(nChild) >> chaincode >> key_prefix >> std::span{ser_key};
+ key.Set(ser_key.begin(), ser_key.end(), true);
+ if ((nDepth == 0 && (nChild != 0 || fingerprint != KeyFingerprint{})) || key_prefix != 0) key = CKey();
+ }
[[nodiscard]] bool Derive(CExtKey& out, unsigned int nChild) const;
CExtPubKey Neuter() const;
void SetSeed(std::span<const std::byte> seed);
### src/key_io.cpp
@@ -8,6 +8,7 @@
#include <bech32.h>
#include <script/interpreter.h>
#include <script/solver.h>
+#include <streams.h>
#include <tinyformat.h>
#include <util/overflow.h>
#include <util/strencodings.h>
@@ -249,7 +250,7 @@ CExtPubKey DecodeExtPubKey(const std::string& str)
if (DecodeBase58Check(str, data, 78)) {
const std::vector<unsigned char>& prefix = Params().Base58Prefix(CChainParams::EXT_PUBLIC_KEY);
if (data.size() == BIP32_EXTKEY_SIZE + prefix.size() && std::equal(prefix.begin(), prefix.end(), data.begin())) {
- key.Decode(data.data() + prefix.size());
+ SpanReader{std::span{data}.subspan(prefix.size())} >> key;
}
}
return key;
@@ -258,9 +259,7 @@ CExtPubKey DecodeExtPubKey(const std::string& str)
std::string EncodeExtPubKey(const CExtPubKey& key)
{
std::vector<unsigned char> data = Params().Base58Prefix(CChainParams::EXT_PUBLIC_KEY);
- size_t size = data.size();
- data.resize(size + BIP32_EXTKEY_SIZE);
- key.Encode(data.data() + size);
+ VectorWriter{data, data.size(), key};
std::string ret = EncodeBase58Check(data);
return ret;
}
@@ -272,7 +271,7 @@ CExtKey DecodeExtKey(const std::string& str)
if (DecodeBase58Check(str, data, 78)) {
const std::vector<unsigned char>& prefix = Params().Base58Prefix(CChainParams::EXT_SECRET_KEY);
if (data.size() == BIP32_EXTKEY_SIZE + prefix.size() && std::equal(prefix.begin(), prefix.end(), data.begin())) {
- key.Decode(data.data() + prefix.size());
+ SpanReader{std::span{data}.subspan(prefix.size())} >> key;
}
}
if (!data.empty()) {
@@ -284,9 +283,7 @@ CExtKey DecodeExtKey(const std::string& str)
std::string EncodeExtKey(const CExtKey& key)
{
std::vector<unsigned char> data = Params().Base58Prefix(CChainParams::EXT_SECRET_KEY);
- size_t size = data.size();
- data.resize(size + BIP32_EXTKEY_SIZE);
- key.Encode(data.data() + size);
+ VectorWriter{data, data.size(), key};
std::string ret = EncodeBase58Check(data);
memory_cleanse(data.data(), data.size());
return ret;
### src/psbt.h
@@ -1285,11 +1285,9 @@ class PartiallySignedTransaction
// Write xpubs
for (const auto& xpub_pair : m_xpubs) {
for (const auto& xpub : xpub_pair.second) {
- unsigned char ser_xpub[BIP32_EXTKEY_WITH_VERSION_SIZE];
- xpub.EncodeWithVersion(ser_xpub);
// Note that the serialization swaps the key and value
// The xpub is the key (for uniqueness) while the path is the value
- SerializeToVector(s, PSBT_GLOBAL_XPUB, ser_xpub);
+ SerializeToVector(s, PSBT_GLOBAL_XPUB, xpub.version, xpub);
SerializeHDKeypath(s, xpub_pair.first);
}
}
@@ -1456,7 +1454,7 @@ class PartiallySignedTransaction
ExpectedKeySize("Global XPUB", key, BIP32_EXTKEY_WITH_VERSION_SIZE + 1);
// Read in the xpub from key
CExtPubKey xpub;
- xpub.DecodeWithVersion(&key.data()[1]);
+ SpanReader{std::span{key}.subspan(1)} >> xpub.version >> xpub;
if (!xpub.pubkey.IsFullyValid()) {
throw std::ios_base::failure("Invalid pubkey");
}
### src/pubkey.cpp
@@ -382,36 +382,6 @@ CPubKey EllSwiftPubKey::Decode() const
return CPubKey{vch_bytes.begin(), vch_bytes.end()};
}
-void CExtPubKey::Encode(unsigned char code[BIP32_EXTKEY_SIZE]) const {
- code[0] = nDepth;
- std::ranges::copy(fingerprint, code+1);
- WriteBE32(code+5, nChild);
- memcpy(code+9, chaincode.begin(), 32);
- assert(pubkey.size() == CPubKey::COMPRESSED_SIZE);
- memcpy(code+41, pubkey.begin(), CPubKey::COMPRESSED_SIZE);
-}
-
-void CExtPubKey::Decode(const unsigned char code[BIP32_EXTKEY_SIZE]) {
- nDepth = code[0];
- std::copy_n(code + 1, fingerprint.size(), fingerprint.begin());
- nChild = ReadBE32(code+5);
- memcpy(chaincode.begin(), code+9, 32);
- pubkey.Set(code+41, code+BIP32_EXTKEY_SIZE);
- if ((nDepth == 0 && (nChild != 0 || ReadLE32(fingerprint.data()) != 0)) || !pubkey.IsFullyValid()) pubkey = CPubKey();
-}
-
-void CExtPubKey::EncodeWithVersion(unsigned char code[BIP32_EXTKEY_WITH_VERSION_SIZE]) const
-{
- memcpy(code, version, 4);
- Encode(&code[4]);
-}
-
-void CExtPubKey::DecodeWithVersion(const unsigned char code[BIP32_EXTKEY_WITH_VERSION_SIZE])
-{
- memcpy(version, code, 4);
- Decode(&code[4]);
-}
-
bool CExtPubKey::Derive(CExtPubKey &out, unsigned int _nChild, uint256* bip32_tweak_out) const {
if (nDepth == std::numeric_limits<unsigned char>::max()) return false;
out.nDepth = nDepth + 1;
### src/pubkey.h
@@ -12,8 +12,11 @@
#include <span.h>
#include <uint256.h>
+#include <array>
+#include <cassert>
#include <cstring>
#include <optional>
+#include <span>
#include <vector>
inline constexpr unsigned int BIP32_EXTKEY_SIZE = 74;
@@ -371,10 +374,21 @@ struct CExtPubKey {
return pubkey.GetID().fingerprint();
}
- void Encode(unsigned char code[BIP32_EXTKEY_SIZE]) const;
- void Decode(const unsigned char code[BIP32_EXTKEY_SIZE]);
- void EncodeWithVersion(unsigned char code[BIP32_EXTKEY_WITH_VERSION_SIZE]) const;
- void DecodeWithVersion(const unsigned char code[BIP32_EXTKEY_WITH_VERSION_SIZE]);
+ //! BIP32 serialization without the version bytes (BIP32_EXTKEY_SIZE bytes)
+ template <typename Stream>
+ void Serialize(Stream& s) const
+ {
+ assert(pubkey.size() == CPubKey::COMPRESSED_SIZE);
+ s << nDepth << fingerprint << Using<BigEndianFormatter<4>>(nChild) << chaincode << std::span{pubkey.data(), CPubKey::COMPRESSED_SIZE};
+ }
+ template <typename Stream>
+ void Unserialize(Stream& s)
+ {
+ std::array<unsigned char, CPubKey::COMPRESSED_SIZE> ser_pubkey;
+ s >> nDepth >> fingerprint >> Using<BigEndianFormatter<4>>(nChild) >> chaincode >> ser_pubkey;
+ pubkey.Set(ser_pubkey.begin(), ser_pubkey.end());
+ if ((nDepth == 0 && (nChild != 0 || fingerprint != KeyFingerprint{})) || !pubkey.IsFullyValid()) pubkey = CPubKey();
+ }
[[nodiscard]] bool Derive(CExtPubKey& out, unsigned int nChild, uint256* bip32_tweak_out = nullptr) const;
};
### src/rpc/rawtransaction.cpp
@@ -1147,8 +1147,7 @@ static RPCMethod decodepsbt()
for (std::pair<KeyOriginInfo, std::set<CExtPubKey>> xpub_pair : psbtx.m_xpubs) {
for (auto& xpub : xpub_pair.second) {
std::vector<unsigned char> ser_xpub;
- ser_xpub.assign(BIP32_EXTKEY_WITH_VERSION_SIZE, 0);
- xpub.EncodeWithVersion(ser_xpub.data());
+ VectorWriter{ser_xpub, 0, xpub.version, xpub};
UniValue keypath(UniValue::VOBJ);
keypath.pushKV("xpub", EncodeBase58Check(ser_xpub));
### src/test/bip32_tests.cpp
@@ -129,9 +129,17 @@ void RunTest(const TestVector& test)
key.SetSeed(seed);
pubkey = key.Neuter();
for (const TestDerivation &derive : test.vDerive) {
- unsigned char data[74];
- key.Encode(data);
- pubkey.Encode(data);
+ // Test serialization round trip
+ DataStream ss{};
+ ss << key;
+ ss << pubkey;
+ BOOST_CHECK_EQUAL(ss.size(), 2 * BIP32_EXTKEY_SIZE);
+ CExtKey key_deser;
+ CExtPubKey pubkey_deser;
+ ss >> key_deser;
+ ss >> pubkey_deser;
+ BOOST_CHECK(key_deser == key);
+ BOOST_CHECK(pubkey_deser == pubkey);
// Test private key
BOOST_CHECK(EncodeExtKey(key) == derive.prv);
@@ -185,6 +193,109 @@ BOOST_AUTO_TEST_CASE(bip32_test5) {
}
}
+BOOST_AUTO_TEST_CASE(bip32_deserialize_invalid)
+{
+ // A serialized extended key is exactly BIP32_EXTKEY_SIZE bytes. A shorter
+ // stream must throw rather than read past the end.
+ for (size_t len{0}; len < BIP32_EXTKEY_SIZE; ++len) {
+ CExtKey key;
+ CExtPubKey pubkey;
+ DataStream ss_key{std::vector<unsigned char>(len)};
+ DataStream ss_pubkey{std::vector<unsigned char>(len)};
+ BOOST_CHECK_THROW(ss_key >> key, std::ios_base::failure);
+ BOOST_CHECK_THROW(ss_pubkey >> pubkey, std::ios_base::failure);
+ }
+
+ // Serialize a valid depth-0 xprv/xpub to mutate below.
+ const CExtKey master{DecodeExtKey(test1.vDerive[0].prv)};
+ const CExtPubKey master_pub{master.Neuter()};
+ BOOST_REQUIRE(master.nDepth == 0);
+ std::vector<unsigned char> key_bytes, pubkey_bytes;
+ {
+ DataStream ss{};
+ ss << master;
+ const auto ss_span{MakeUCharSpan(ss)};
+ key_bytes.assign(ss_span.begin(), ss_span.end());
+ DataStream sp{};
+ sp << master_pub;
+ const auto sp_span{MakeUCharSpan(sp)};
+ pubkey_bytes.assign(sp_span.begin(), sp_span.end());
+ }
+ BOOST_CHECK_EQUAL(key_bytes.size(), BIP32_EXTKEY_SIZE);
+ BOOST_CHECK_EQUAL(pubkey_bytes.size(), BIP32_EXTKEY_SIZE);
+
+ // A longer stream is not invalid: exactly BIP32_EXTKEY_SIZE bytes are consumed
+ // and the trailing byte is left unread.
+ {
+ std::vector<unsigned char> extra{key_bytes};
+ extra.push_back(0);
+ DataStream ss{extra};
+ CExtKey key;
+ ss >> key;
+ BOOST_CHECK(key == master);
+ BOOST_CHECK_EQUAL(ss.size(), 1);
+ }
+ {
+ std::vector<unsigned char> extra{pubkey_bytes};
+ extra.push_back(0);
+ DataStream ss{extra};
+ CExtPubKey pubkey;
+ ss >> pubkey;
+ BOOST_CHECK(pubkey == master_pub);
+ BOOST_CHECK_EQUAL(ss.size(), 1);
+ }
+
+ const auto deser_extkey{[](std::vector<unsigned char> bytes) {
+ DataStream ss{bytes};
+ CExtKey key;
+ ss >> key;
+ return key;
+ }};
+ const auto deser_extpubkey{[](std::vector<unsigned char> bytes) {
+ DataStream ss{bytes};
+ CExtPubKey pubkey;
+ ss >> pubkey;
+ return pubkey;
+ }};
+
+ // Non-zero private key prefix (offset 41) => invalid.
+ {
+ auto bytes{key_bytes};
+ bytes[41] = 1;
+ BOOST_CHECK(!deser_extkey(bytes).key.IsValid());
+ }
+ // Non-zero child index with nDepth == 0 (offset 8) => invalid.
+ {
+ auto bytes{key_bytes};
+ bytes[8] = 1;
+ BOOST_CHECK(!deser_extkey(bytes).key.IsValid());
+ }
+ // Non-zero parent fingerprint with nDepth == 0 (offset 1) => invalid.
+ {
+ auto bytes{key_bytes};
+ bytes[1] = 1;
+ BOOST_CHECK(!deser_extkey(bytes).key.IsValid());
+ }
+ // Invalid public key header (offset 41) => invalid.
+ {
+ auto bytes{pubkey_bytes};
+ bytes[41] = 0;
+ BOOST_CHECK(!deser_extpubkey(bytes).pubkey.IsValid());
+ }
+ // Non-zero child index with nDepth == 0 (offset 8) => invalid.
+ {
+ auto bytes{pubkey_bytes};
+ bytes[8] = 1;
+ BOOST_CHECK(!deser_extpubkey(bytes).pubkey.IsValid());
+ }
+ // Non-zero parent fingerprint with nDepth == 0 (offset 1) => invalid.
+ {
+ auto bytes{pubkey_bytes};
+ bytes[1] = 1;
+ BOOST_CHECK(!deser_extpubkey(bytes).pubkey.IsValid());
+ }
+}
+
BOOST_AUTO_TEST_CASE(bip32_derive_ext_key)
{
const CExtKey master{DecodeExtKey(test1.vDerive[0].prv)};
### src/test/fuzz/script_descriptor_cache.cpp
@@ -4,6 +4,7 @@
#include <pubkey.h>
#include <script/descriptor.h>
+#include <streams.h>
#include <test/fuzz/FuzzedDataProvider.h>
#include <test/fuzz/fuzz.h>
#include <test/fuzz/util.h>
@@ -21,7 +22,7 @@ FUZZ_TARGET(script_descriptor_cache)
const std::vector<uint8_t> code = fuzzed_data_provider.ConsumeBytes<uint8_t>(BIP32_EXTKEY_SIZE);
if (code.size() == BIP32_EXTKEY_SIZE) {
CExtPubKey xpub;
- xpub.Decode(code.data());
+ SpanReader{code} >> xpub;
const uint32_t key_exp_pos = fuzzed_data_provider.ConsumeIntegral<uint32_t>();
CExtPubKey xpub_fetched;
if (fuzzed_data_provider.ConsumeBool()) {
### src/wallet/test/walletload_tests.cpp
@@ -2,6 +2,8 @@
// Distributed under the MIT software license, see the accompanying
// file COPYING or https://www.opensource.org/licenses/mit-license.php.
+#include <key.h>
+#include <script/descriptor.h>
#include <wallet/test/util.h>
#include <wallet/wallet.h>
#include <test/util/common.h>
@@ -84,5 +86,48 @@ BOOST_FIXTURE_TEST_CASE(wallet_load_descriptors, TestingSetup)
}
}
+BOOST_FIXTURE_TEST_CASE(wallet_load_descriptor_cache_invalid_xpub_size, TestingSetup)
+{
+ // A cache record whose serialized xpub is not exactly BIP32_EXTKEY_SIZE bytes must be
+ // rejected as corruption.
+ bilingual_str error;
+ std::vector<bilingual_str> warnings;
+
+ const std::string desc_str = "wpkh([d34db33f/84h/0h/0h]xpub6DJ2dNUysrn5Vt36jH2KLBT2i1auw1tTSSomg8PhqNiUtx8QX2SvC9nrHu81fT41fvDUnhMjEzQgXnQjKEu3oaqMSzhSrHMxyyoEAmUHQbY/0/*)#cjjspncu";
+ FlatSigningProvider keys;
+ std::string parse_error;
+ std::vector<std::unique_ptr<Descriptor>> descs = Parse(desc_str, keys, parse_error, /*require_checksum=*/true);
+ BOOST_REQUIRE_MESSAGE(descs.size() == 1, parse_error);
+ std::shared_ptr<Descriptor> descriptor = std::move(descs.at(0));
+ const uint256 desc_id = CompatDescriptorHash(*descriptor);
+
+ auto make_db_with_short_cache_xpub = [&](const std::string& cache_type) {
+ std::unique_ptr<WalletDatabase> database = CreateMockableWalletDatabase();
+ {
+ WalletBatch batch(*database);
+ WalletDescriptor wallet_descriptor(descriptor, 0, 0, 0, 0);
+ BOOST_CHECK(batch.WriteDescriptor(desc_id, wallet_descriptor));
+ }
+ const std::vector<unsigned char> short_xpub(BIP32_EXTKEY_SIZE - 1, 0);
+ std::unique_ptr<DatabaseBatch> raw = database->MakeBatch();
+ BOOST_CHECK(raw->Write(std::make_pair(std::make_pair(cache_type, desc_id), uint32_t{0}), short_xpub));
+ return database;
+ };
+
+ {
+ std::unique_ptr<WalletDatabase> database = make_db_with_short_cache_xpub("walletdescriptorcache");
+ ASSERT_DEBUG_LOG("descriptor cache xpub has invalid size");
+ const std::shared_ptr<CWallet> wallet(new CWallet(m_node.chain.get(), "", std::move(database)));
+ BOOST_CHECK_EQUAL(wallet->PopulateWalletFromDB(error, warnings), DBErrors::CORRUPT);
+ }
+
+ {
+ std::unique_ptr<WalletDatabase> database = make_db_with_short_cache_xpub("walletdescriptorlhcache");
+ ASSERT_DEBUG_LOG("descriptor last hardened cache xpub has invalid size");
+ const std::shared_ptr<CWallet> wallet(new CWallet(m_node.chain.get(), "", std::move(database)));
+ BOOST_CHECK_EQUAL(wallet->PopulateWalletFromDB(error, warnings), DBErrors::CORRUPT);
+ }
+}
+
BOOST_AUTO_TEST_SUITE_END()
} // namespace wallet
### src/wallet/walletdb.cpp
@@ -255,22 +255,22 @@ bool WalletBatch::WriteDescriptor(const uint256& desc_id, const WalletDescriptor
bool WalletBatch::WriteDescriptorDerivedCache(const CExtPubKey& xpub, const uint256& desc_id, uint32_t key_exp_index, uint32_t der_index)
{
- std::vector<unsigned char> ser_xpub(BIP32_EXTKEY_SIZE);
- xpub.Encode(ser_xpub.data());
+ std::vector<unsigned char> ser_xpub;
+ VectorWriter{ser_xpub, 0, xpub};
return WriteIC(std::make_pair(std::make_pair(DBKeys::WALLETDESCRIPTORCACHE, desc_id), std::make_pair(key_exp_index, der_index)), ser_xpub);
}
bool WalletBatch::WriteDescriptorParentCache(const CExtPubKey& xpub, const uint256& desc_id, uint32_t key_exp_index)
{
- std::vector<unsigned char> ser_xpub(BIP32_EXTKEY_SIZE);
- xpub.Encode(ser_xpub.data());
+ std::vector<unsigned char> ser_xpub;
+ VectorWriter{ser_xpub, 0, xpub};
return WriteIC(std::make_pair(std::make_pair(DBKeys::WALLETDESCRIPTORCACHE, desc_id), key_exp_index), ser_xpub);
}
bool WalletBatch::WriteDescriptorLastHardenedCache(const CExtPubKey& xpub, const uint256& desc_id, uint32_t key_exp_index)
{
- std::vector<unsigned char> ser_xpub(BIP32_EXTKEY_SIZE);
- xpub.Encode(ser_xpub.data());
+ std::vector<unsigned char> ser_xpub;
+ VectorWriter{ser_xpub, 0, xpub};
return WriteIC(std::make_pair(std::make_pair(DBKeys::WALLETDESCRIPTORLHCACHE, desc_id), key_exp_index), ser_xpub);
}
@@ -811,10 +811,13 @@ static DBErrors LoadDescriptorWalletRecords(CWallet* pwallet, DatabaseBatch& bat
}
catch (...) {}
- std::vector<unsigned char> ser_xpub(BIP32_EXTKEY_SIZE);
- value >> ser_xpub;
+ // The xpub is stored as a length-prefixed byte vector
+ if (ReadCompactSize(value) != BIP32_EXTKEY_SIZE) {
+ err = "Error reading wallet database: descriptor cache xpub has invalid size";
+ return DBErrors::CORRUPT;
+ }
CExtPubKey xpub;
- xpub.Decode(ser_xpub.data());
+ value >> xpub;
if (parent) {
cache.CacheParentExtPubKey(key_exp_index, xpub);
} else {
@@ -834,10 +837,13 @@ static DBErrors LoadDescriptorWalletRecords(CWallet* pwallet, DatabaseBatch& bat
assert(desc_id == id);
key >> key_exp_index;
- std::vector<unsigned char> ser_xpub(BIP32_EXTKEY_SIZE);
- value >> ser_xpub;
+ // The xpub is stored as a length-prefixed byte vector
+ if (ReadCompactSize(value) != BIP32_EXTKEY_SIZE) {
+ err = "Error reading wallet database: descriptor last hardened cache xpub has invalid size";
+ return DBErrors::CORRUPT;
+ }
CExtPubKey xpub;
- xpub.Decode(ser_xpub.data());
+ value >> xpub;
cache.CacheLastHardenedExtPubKey(key_exp_index, xpub);
return DBErrors::LOAD_OK;
});Why this scored 45/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.