From 7fcaccd9d0b7b9ec80b8224239aeb3c13b90593d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C5=91rinc?= Date: Thu, 27 Aug 2026 21:29:25 -0500 Subject: [PATCH] bech32: bound overlength error locations `LocateErrors()` returns multiple useful positions for character and checksum errors, but an overlength string has one structural error. Every character from the limit onward is outside the permitted address, so listing each position adds no diagnostic value. `validateaddress` converts every returned position into a `UniValue` number before serializing the response. An authenticated request below the HTTP body limit can therefore require several gigabytes of memory. Return only the first position beyond the length limit, which identifies where the violation begins. Character and checksum errors continue to report multiple useful positions when they can be determined, and the existing unit and functional tests cover both behaviors. --- src/bech32.cpp | 4 +--- src/test/bech32_tests.cpp | 2 ++ test/functional/rpc_invalid_address_message.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/bech32.cpp b/src/bech32.cpp index ed918622176..9c784c3ec8c 100644 --- a/src/bech32.cpp +++ b/src/bech32.cpp @@ -8,7 +8,6 @@ #include #include -#include #include namespace bech32 @@ -404,8 +403,7 @@ std::pair> LocateErrors(const std::string& str, Ch std::vector error_locations{}; if (str.size() > limit) { - error_locations.resize(str.size() - limit); - std::iota(error_locations.begin(), error_locations.end(), static_cast(limit)); + error_locations.push_back(static_cast(limit)); return std::make_pair("Bech32 string too long", std::move(error_locations)); } diff --git a/src/test/bech32_tests.cpp b/src/test/bech32_tests.cpp index 795510cfa21..3b666065309 100644 --- a/src/test/bech32_tests.cpp +++ b/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> 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"); diff --git a/test/functional/rpc_invalid_address_message.py b/test/functional/rpc_invalid_address_message.py index 25d8a68a1aa..dd9758bbced 100755 --- a/test/functional/rpc_invalid_address_message.py +++ b/test/functional/rpc_invalid_address_message.py @@ -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])