Merge bitcoin/bitcoin#36111: rpc: bound memory for overlong Bech32 errors

7fcaccd9d0 bech32: bound overlength error locations (Lőrinc)

Pull request description:

  **Problem:** `validateaddress` reports likely error positions for invalid Bech32 inputs, including multiple useful positions for character and checksum errors.
  For an overlength input, `LocateErrors()` returns every position after the 90-character limit, which the RPC converts to a `UniValue` number before serializing the response.
  A near-limit authenticated request therefore creates about 33 million `int` values and 33 million `UniValue` objects.

  **Fix:** Return position 90 for an overlength input, which identifies where the single length violation begins.
  Character and checksum errors continue to return multiple useful positions when they can be determined.
  The tests now include an oversized example and pin the bounded result.

  **Reproducer:** Peak memory usage for a near-limit authenticated request:

  <details>
  <summary>Linux reproducer</summary>

  ```bash
  sed -i "/def test_validateaddress(self):/a\\
          self.nodes[0].validateaddress('bcrt1' + 'q' * (2**25 - 100))\\
          __import__('time').sleep(30)" test/functional/rpc_invalid_address_message.py
  cmake -B build && cmake --build build -j2
  build/test/functional/rpc_invalid_address_message.py >/dev/null 2>&1 &
  sleep 20 && awk '/VmHWM/' /proc/$(pgrep bitcoind)/status
  ```
  </details>

  ```text
  Before ████████████████████████ 5.69 GiB
  After  █░░░░░░░░░░░░░░░░░░░░░░░  240 MiB
  ```

ACKs for top commit:
  maflcko:
    lgtm ACK 7fcaccd9d0
  sedited:
    ACK 7fcaccd9d0
  janb84:
    ACK 7fcaccd9d0

Tree-SHA512: 3d439774d394f081b8107f8131963f7aa23ed048b0d6d349a80f9b3481fefeef7b5ce239fbd33606ad1f4960e6bfd899f968c50febc70d38b2fe731c6049583f
This commit is contained in:
merge-script
2026-08-29 10:20:42 +02:00
3 changed files with 4 additions and 4 deletions

View File

@@ -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));
}

View File

@@ -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");

View File

@@ -68,7 +68,7 @@ class InvalidAddressErrorMessageTest(BitcoinTestFramework):
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])