Merge bitcoin/bitcoin#36111: rpc: bound memory for overlong Bech32 errors
What changed, and why it matters
This patch fixes a memory blow-up in Bitcoin Core's address-validation RPC. When someone sent a Bech32 address longer than 90 characters, the code used to list every extra character position as a separate error location, creating tens of millions of objects and using several gigabytes of RAM. The fix now reports only position 90 as the single length-violation point, capping memory use. It is a denial-of-service-style resource exhaustion issue, but it requires an authenticated RPC caller.
Apply the patch. It is a minimal, well-tested change. Operators running nodes with exposed authenticated RPC should also restrict RPC access and consider rate-limiting large `validateaddress` requests until patched.
Security signals we found
Unbounded memory allocation proportional to attacker-controlled input size
Resource exhaustion via authenticated RPC endpoint
Fix explicitly bounds error-location output to a single entry
Reproducer demonstrates multi-gigabyte peak memory before patch
Evidence from the diff
In src/bech32.cpp, LocateErrors() previously allocated a vector of size str.size() - limit and filled it with all indices from limit onward for overlength Bech32 strings. For a 33-million-character input this produced ~33 million integers, which validateaddress then wrapped in UniValue objects, spiking memory to ~5.7 GiB. The patch replaces that with a single error_locations.push_back(limit), returning only the first overlength position. Unit and functional tests are updated to expect {90} instead of range(90, 108).
Changed components
src/bech32.cppsrc/test/bech32_tests.cpptest/functional/rpc_invalid_address_message.pyRPC validateaddressInspect captured patch +4 / −4
### src/bech32.cpp
@@ -8,7 +8,6 @@
#include <array>
#include <cassert>
-#include <numeric>
#include <optional>
namespace bech32
@@ -404,8 +403,7 @@ std::pair<std::string, std::vector<int>> LocateErrors(const std::string& str, Ch
std::vector<int> error_locations{};
if (str.size() > limit) {
- error_locations.resize(str.size() - limit);
- std::iota(error_locations.begin(), error_locations.end(), static_cast<int>(limit));
+ error_locations.push_back(static_cast<int>(limit));
return std::make_pair("Bech32 string too long", std::move(error_locations));
}
### src/test/bech32_tests.cpp
@@ -71,6 +71,7 @@ BOOST_AUTO_TEST_CASE(bech32_testvectors_invalid)
"A12uEL5L",
"abcdef1qpzrz9x8gf2tvdw0s3jn54khce6mua7lmqqqxw",
"test1zg69w7y6hn0aqy352euf40x77qddq3dc",
+ std::string(100, 'q'),
};
static const std::pair<std::string, std::vector<int>> ERRORS[] = {
{"Invalid character or mixed case", {0}},
@@ -89,6 +90,7 @@ BOOST_AUTO_TEST_CASE(bech32_testvectors_invalid)
{"Invalid character or mixed case", {3}},
{"Invalid Bech32 checksum", {11}},
{"Invalid Bech32 checksum", {9, 16}},
+ {"Bech32 string too long", {90}},
};
static_assert(std::size(CASES) == std::size(ERRORS), "Bech32 CASES and ERRORS should have the same length");
### test/functional/rpc_invalid_address_message.py
@@ -68,7 +68,7 @@ def test_validateaddress(self):
self.check_invalid(BECH32_INVALID_BECH32M, 'Version 0 witness address must use Bech32 checksum')
self.check_invalid(BECH32_INVALID_VERSION, 'Invalid Bech32 address witness version')
self.check_invalid(BECH32_INVALID_V0_SIZE, "Invalid Bech32 v0 address program size (21 bytes), per BIP141")
- self.check_invalid(BECH32_TOO_LONG, 'Bech32 string too long', list(range(90, 108)))
+ self.check_invalid(BECH32_TOO_LONG, 'Bech32 string too long', [90])
self.check_invalid(BECH32_ONE_ERROR, 'Invalid Bech32 checksum', [9])
self.check_invalid(BECH32_TWO_ERRORS, 'Invalid Bech32 checksum', [22, 43])
self.check_invalid(BECH32_ONE_ERROR_CAPITALS, 'Invalid Bech32 checksum', [38])Why this scored 62/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.