Skip to content

fix(jsonrpc): harden RPC/HTTP parameter validation#6828

Merged
kuny0707 merged 7 commits into
tronprotocol:release_v4.8.2from
0xbigapple:fix/rpc-param-validation-guards
Jun 11, 2026
Merged

fix(jsonrpc): harden RPC/HTTP parameter validation#6828
kuny0707 merged 7 commits into
tronprotocol:release_v4.8.2from
0xbigapple:fix/rpc-param-validation-guards

Conversation

@0xbigapple

@0xbigapple 0xbigapple commented Jun 9, 2026

Copy link
Copy Markdown
Collaborator

What does this PR do?

Adds length/format validation to several JSON-RPC and HTTP parameter paths, so malformed,
oversized, or overflowing input is rejected early — a clear -32602 (or a null result for an
out-of-range index) — instead of triggering O(n²) work, leaking a NullPointerException /
DecoderException / Internal error, or silently producing a wrong value. Valid input is unaffected.

  1. Commons.decodeFromBase58Check — reject input whose length != 34 before the O(n²) Base58.decode; single funnel for every HTTP address input.
  2. JsonFormat.unescapeBytesSelfType — an invalid Base58 address (null / illegal char) now returns a clear invalid address error instead of a bare ByteString.copyFrom(null) NPE; covers all merge-path address fields.
  3. JsonRpcApiUtil.addressCompatibleToByteArray — cap length at ADDRESS_SIZE + 2 (the +2 keeps 0x-prefixed 21-byte addresses valid) before fromHexString.
  4. JsonRpcApiUtil.parseBlockNumber — tighten MAX_BLOCK_NUM_HEX_LEN 100 → 20, and route the (String, Wallet) overload through the single-arg parser so it also gets the length cap checks.
  5. TronJsonRpcImpl.getStorageAt — bound the storage key and parse it before the contract lookup; a >32-byte key now returns -32602 invalid storage key value instead of -32001 (json4j default error).
  6. LogFilterWrapper — validate blockHash via the shared JsonRpcApiUtil.hashToByteArray (moved here from TronJsonRpcImpl).
  7. HASH_REGEX accepts only hex digits [0-9a-fA-F], so a non-hex hash is rejected as invalid hash value rather than slipping through to fromHexString.
  8. JsonRpcApiUtil.parseTxIndex — length-capped 0x-hex parse for the tx index; a malformed / oversized / int-overflow index returns -32602, and a negative or out-of-range index returns a null result instead of letting block.getTransactions(-1) throw IndexOutOfBoundsException (Internal error).
  9. TronJsonRpcImpl.buildCreateSmartContractTransaction — a deeply nested ABI overflows the recursive JsonFormat parser; the StackOverflowError is caught narrowly around the merge and mapped to -32602 invalid abi instead of an Error escaping as an Internal error.
  10. JsonRpcApiUtil.calcFeeLimit — compute feeLimit = gas * energyFee via StrictMathWrapper.multiplyExact, so an oversized gas returns -32602 instead of silently wrapping to a bogus (possibly negative) feeLimit.
  11. JsonRpcApiUtil.parseQuantityValue — reject a signed (0x-..) value / gas; QUANTITY is unsigned, so a negative parse is -32602 rather than a negative callValue / feeLimit reaching the actuator.
  12. LogFilter topicstopicToByteArray now validates topics via TOPIC_REGEX (^(0x)?[0-9a-fA-F]{63,64}$); an invalid-hex / wrong-length / non-string topic returns -32602 instead of a leaked DecoderException / ClassCastException, while keeping the odd-length (63-char) leading-0 compatibility.
  13. JsonRpcApiUtil.addressToByteArray (log-filter address) — cap length before fromHexString and wrap the decode, so an oversized or invalid-hex address filter returns -32602 instead of O(n) work or a leaked DecoderException;a nested non-string address element (e.g. {"address":[1]}) is likewise rejected as -32602 before the (String) cast; the 20-byte output and strict (no 41 prefix) semantics are unchanged.
  14. getBlockReceipts + parseBlockNumber(String, Wallet) — a null block param dereferenced before validation (Pattern.matches(HASH_REGEX, null) / null.startsWith("0x")) NPE'd as an Internal error; it now returns -32602.

Why are these changes required?

Several unauthenticated API entry points accepted unbounded / malformed input, causing two classes of problem:

  • Algorithmic-complexity DoSdecodeFromBase58Check fed the whole string into the O(n²)
    Base58.decode; since the cost grows quadratically with length, a single oversized address
    payload (well within the 4 MB body cap) can tie up a request thread far out of proportion to
    its size — a cheap DoS the body cap and rate limiter don't stop. Block-number parsing likewise
    had no effective length bound before the O(n²) BigInteger constructor.
  • Leaked exceptions / unfriendly errors — an invalid address on the merge path returned a
    bare NullPointerException; eth_getStorageAt surfaced a RuntimeException as
    -32001 Data word can't exceed 32 bytes: xxx; a negative tx index hit getTransactions(-1)
    (IndexOutOfBoundsException); a malformed log topic / address leaked a DecoderException; a
    deeply nested ABI exhausted the recursive JsonFormat parser's stack (StackOverflowError); a
    null block param NPE'd on Pattern.matches / startsWith — all escaping as Internal errors
    instead of -32602 invalid params.

This PR has been tested by:

  • Unit Tests
  • Manual Testing

Follow up

Extra details

Representative affected endpoints, by guarded parameter (each parameter is a single guarded
funnel, so the lists below are examples, not exhaustive):

  • JSON-RPC
    • block number / tag (length 100 → 20): eth_getBlockByNumber,getBlockReceipts, …
    • block hash (hashToByteArray): eth_getBlockByHash, eth_getTransactionByBlockHashAndIndex, …
    • tx hash (hashToByteArray): eth_getTransactionByHash, eth_getTransactionReceipt
    • storage index (parsed as a 32-byte word): eth_getStorageAt
    • address (length capped at ADDRESS_SIZE + 2): eth_call, eth_getBalance
    • tx index (length-capped; negative / out-of-range → null): eth_getTransactionByBlockHashAndIndex, eth_getTransactionByBlockNumberAndIndex
    • log topic / log address (strict hashToByteArray / length-capped addressToByteArray): eth_getLogs, eth_newFilter
    • gas / value / abi (overflow-checked feeLimit, unsigned quantity, ABI-depth guard): buildTransaction
    • block param null (null-32602): eth_getBlockReceipts
  • HTTP /wallet/* — all covered by the Base58 length/DoS guard, but they differ on an invalid address:
    • most endpoints pre-convert via Util.getHexAddress and just return {} (e.g. getcontract) — unchanged;
    • endpoints that merge a Base58 field directly (e.g. getaccount, visible=true) get the NPE → invalid address fix;
    • endpoints that resolve the address via Util.getAddress(request) (e.g. getReward) get null for an invalid address and degrade to reward 0.

  Add length/format guards across the JSON-RPC and HTTP address-handling paths so
  malformed or oversized parameters are rejected early with a clear -32602 / error
  response, instead of triggering O(n^2) work or leaking NPE / Internal errors.

  - Commons.decodeFromBase58Check: reject non-34-char input before the O(n^2)
    Base58 decode; single funnel covering every HTTP address input.
  - JsonFormat.unescapeBytesSelfType: turn an invalid Base58 address (null or
    illegal-char) into a clear "invalid address" error instead of a bare
    ByteString.copyFrom(null) NPE; covers all merge-path address fields.
  - JsonRpcApiUtil.addressCompatibleToByteArray: cap length at ADDRESS_SIZE + 2
    (the +2 keeps 0x-prefixed 21-byte addresses valid) before fromHexString.
  - JsonRpcApiUtil.parseBlockNumber: tighten MAX_BLOCK_NUM_HEX_LEN 100 -> 20 and
    route the (String, Wallet) overload through the single-arg parser so it also
    gets the length cap, overflow (longValueExact) and negative checks.
  - TronJsonRpcImpl.getStorageAt: bound the storage key and parse it before the
    contract lookup; wrap DataWord parsing so a >32-byte key
    returns invalid-params instead of an Internal error.
  - Hoist hashToByteArray/HASH_REGEX into JsonRpcApiUtil so LogFilterWrapper's
    blockHash gets the same 32-byte-hash validation.
@github-actions github-actions Bot requested a review from bladehan1 June 9, 2026 04:15
…and address

  Continue hardening the eth_* JSON-RPC parameter paths so malformed, oversized
  or overflowing inputs are rejected with a clear -32602 (or, for an out-of-range
  index, a null result) instead of leaking an Internal error, an uncaught
  Error/NPE, a DecoderException, or a silently wrong value.

  - eth_getTransactionByBlock{Hash,Number}AndIndex: parse the index via a new
    length-capped JsonRpcApiUtil.parseTxIndex (malformed / oversized / int-overflow
    index -> -32602); in the handler, a negative or out-of-range index
    (txIndex < 0 || >= tx count) returns a null result instead of letting
    block.getTransactions(-1) throw IndexOutOfBoundsException (-32603).
  - buildCreateSmartContractTransaction: a deeply nested ABI overflows the
    recursive JsonFormat parser; catch the StackOverflowError narrowly around the
    merge and map it to invalid-params ("invalid abi") instead of letting an
    Error escape.
  - buildCreate/buildTriggerSmartContractTransaction: compute feeLimit via
    JsonRpcApiUtil.calcFeeLimit (Math.multiplyExact) so an oversized gas no
    longer silently wraps gas*energyFee to a bogus (possibly negative) feeLimit.
  - parseQuantityValue: reject a signed ("0x-..") value/gas; QUANTITY is unsigned.
  - LogFilter topics: parse via the strict hashToByteArray (regex-validated 32-byte
    hash) instead of topicToByteArray, and remove the now-unused topicToByteArray.
  - addressToByteArray: cap length before fromHexString and wrap the decode so an
    oversized or invalid-hex address filter is invalid-params.
…er elements

 - Restore topicToByteArray for LogFilter topics, guard with (0x)?[0-9a-fA-F]{63,64}$ so the stripped zero is padded back by ByteArray.fromHexString, while non-hex or wrong-length input still gets a clean -32602.
 - LogFilter: validate element types before the (String) cast in the address array and nested topic array loops.
* check if topic is hex string of size 64, padding 0 ahead if length is odd.
* Matches a 32-byte topic hex string: optional 0x prefix + 63 or 64 hex chars.
*/
public static final String TOPIC_REGEX = "(0x)?[0-9a-fA-F]{63,64}$";

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[SHOULD] PR description item 12 contradicts the final implementation

The description says topics are now parsed "via the strict hashToByteArray ... instead of topicToByteArray (now removed)" and that odd-length input is rejected ("no silent leading-0 pad"). The final code does the opposite: topicToByteArray is kept, TOPIC_REGEX accepts 63 or 64 hex chars, and a 63-char topic is accepted and zero-padded (locked in by topicToByteArrayPadsMissingLeadingZero). Anyone auditing the behavior change from the description will reach the wrong conclusion about what eth_getLogs / eth_newFilter accept.

Suggestion: Update description item 12 to match the code: topics still go through topicToByteArray, which now enforces a 63/64-char length cap and strict hex, while keeping the odd-length leading-zero compatibility.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated the PR description.

Comment thread chainbase/src/main/java/org/tron/common/utils/Commons.java Outdated
Comment thread framework/src/main/java/org/tron/core/services/jsonrpc/JsonRpcApiUtil.java Outdated
@github-actions github-actions Bot requested a review from bladehan1 June 11, 2026 07:10
@0xbigapple 0xbigapple requested a review from waynercheung June 11, 2026 07:29

@bladehan1 bladehan1 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@waynercheung waynercheung left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@kuny0707 kuny0707 merged commit ea3ffb4 into tronprotocol:release_v4.8.2 Jun 11, 2026
13 of 14 checks passed
@halibobo1205 halibobo1205 added this to the GreatVoyage-v4.8.2 milestone Jun 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants