Skip to content

fix(api): input validation and rate limiting#11

Open
barbatos2011 wants to merge 4 commits intodevelopfrom
fix/api-input-validation
Open

fix(api): input validation and rate limiting#11
barbatos2011 wants to merge 4 commits intodevelopfrom
fix/api-input-validation

Conversation

@barbatos2011
Copy link
Copy Markdown
Owner

What

Add input validation, request limiting, and security hardening for HTTP and JSON-RPC APIs. All JSON-RPC limits are aligned with go-ethereum (geth) defaults for ecosystem compatibility.

Why

Multiple API-layer vulnerabilities identified in security audit enable resource exhaustion attacks:

  • Unbounded hex input: hexToBigInteger accepts arbitrary-length strings, allowing attackers to force large BigInteger memory allocations via JSON-RPC parameters
  • No HTTP body size limit: Util.checkBodySize() validates after the body is fully buffered into memory, and only 30% of servlets call it. Oversized requests consume memory before being rejected
  • No JSON-RPC batch limit: A single request containing thousands of batch items can monopolize server resources
  • No response size limit: Queries returning large result sets (e.g., eth_getLogs with broad filters) can generate unbounded response bodies
  • No request timeout: Long-running queries block threads indefinitely
  • Unrestricted class loading: RateLimiterServlet.Class.forName() loads any class name from config before validation, enabling arbitrary class instantiation
  • Free computation DoS: triggerConstantContract costs no TRX, defaults to 100M energy (~100s CPU), and has no concurrency limit

Changes

Commit 1: Input validation and HTTP body size limit

  • TronJsonRpcImpl: add MAX_HEX_PARAM_LENGTH=128 check before all 5 hexToBigInteger call sites
  • Wallet.getByJsonBlockId: same hex length check
  • RequestBodySizeLimitFilter (new): checks Content-Length header, returns HTTP 413 if exceeds limit (default 5MB, aligned with geth defaultBodyLimit)
  • HttpService.addFilter: register body size filter in parent class; all 7 HttpService subclasses inherit via super.addFilter(). Five subclasses that override addFilter() now call super.addFilter(context) first

Commit 2: JSON-RPC request limits (aligned with geth)

  • JsonRpcServlet: rewrite doPost() with three-layer protection:
    • Batch size check (default 1000, geth: BatchRequestLimit=1000, error -32600)
    • Response size limit via BufferedResponseWrapper (default 25MB, geth: BatchResponseMaxSize=25MB, error -32003)
    • Request timeout via ExecutorService + Future.get() (default 30s, geth: WriteTimeout=30s, error -32002)
  • CachedBodyRequestWrapper (new): replays pre-read body bytes for jsonrpc4j after batch size inspection
  • BufferedResponseWrapper (new): wraps response output, throws ResponseTooLargeException eagerly during writes to bound memory
  • LogFilter: reject eth_getLogs filter with >1000 addresses (aligned with geth LogQueryLimit=1000)
  • 4 new configurable parameters: node.jsonrpc.maxBatchSize, maxResponseSize, maxRequestTimeout, maxAddressSize

Commit 3: Security hardening

  • RateLimiterServlet: validate adapter class name against ALLOWED_ADAPTERS whitelist (GlobalPreemptibleAdapter, QpsRateLimiterAdapter, IPQPSRateLimiterAdapter, DefaultBaseQqsAdapter) before Class.forName() to prevent arbitrary class loading
  • Wallet.triggerConstantContract: add Semaphore with tryAcquire()/release() (default 8 concurrent). Original logic moved to doTriggerConstantContract(). Rejects excess calls immediately with ContractValidateException instead of blocking
  • New config: vm.maxConcurrentConstantCalls

Commit 4: Config template with recommended security settings

  • Add all new config items to config.conf as commented-out entries with descriptions
  • Add recommended rate limiter entries for TriggerConstantContractServlet (QPS=20) and EstimateEnergyServlet (QPS=10)
  • Add recommended values in comments for global.qps (10000), global.ip.qps (1000), maxEnergyLimitForConstant (10000000)
  • No code defaults changed — all recommendations are opt-in via config, avoiding impact on existing nodes

Scope

  • Does NOT change any consensus rules or transaction validation logic
  • Does NOT modify protobuf definitions or gRPC interfaces
  • All new limits have configurable defaults; setting to 0 disables the limit
  • Existing nodes upgrading without config changes will get: hex length check (128), HTTP body limit (5MB), JSON-RPC batch limit (1000), response limit (25MB), timeout (30s), address limit (1000), class loading whitelist, constant call concurrency limit (8). All are generous defaults that should not affect legitimate usage

Test

  • All existing framework tests pass (2329 tests, 2 pre-existing net module flaky failures unrelated to this PR)
  • Added 4 new test classes, 13 new test methods:
    • RequestBodySizeLimitFilterTest: normal/oversized/exact-limit/missing-content-length requests
    • HexParamValidationTest: normal hex, 128-char boundary, oversized hex
    • JsonRpcLimitsTest: CachedBodyRequestWrapper replay, BufferedResponseWrapper normal/exceeds/no-limit
    • RateLimiterWhitelistTest: whitelist contains expected adapters, blocks unknown names
  • Existing tests verified: JsonrpcServiceTest (8 methods), HttpApiAccessFilterTest, ArgsTest, WalletMockTest
  • Checkstyle passes (main + test)

Barbatos added 3 commits April 2, 2026 09:38
- TronJsonRpcImpl: add MAX_HEX_PARAM_LENGTH=128 check before all 5
  hexToBigInteger calls to prevent large memory allocation from
  oversized hex strings
- Wallet.getByJsonBlockId: same hex length check
- RequestBodySizeLimitFilter: new filter checking Content-Length
  before buffering, returns HTTP 413 if exceeds limit (default 5MB,
  aligned with geth defaultBodyLimit)
- HttpService.addFilter: register body size filter in parent class,
  all 7 subclasses inherit via super.addFilter()
- JsonRpcServlet: rewrite doPost() with batch size check (default 1000),
  response size limit via BufferedResponseWrapper (default 25MB),
  request timeout via ExecutorService (default 30s). All defaults
  aligned with geth. Error codes: -32600, -32003, -32002
- CachedBodyRequestWrapper: replay pre-read body for jsonrpc4j
- BufferedResponseWrapper: eagerly check response size during writes
- LogFilter: reject eth_getLogs with >1000 addresses (aligned with geth)
- 4 new config items: node.jsonrpc.maxBatchSize, maxResponseSize,
  maxRequestTimeout, maxAddressSize
…imit

- RateLimiterServlet: validate adapter class name against ALLOWED_ADAPTERS
  whitelist before Class.forName to prevent arbitrary class loading
- Wallet.triggerConstantContract: add Semaphore (default 8 concurrent)
  with tryAcquire/release to limit constant call parallelism and
  mitigate free-computation DoS. Original logic moved to
  doTriggerConstantContract()
- New config: vm.maxConcurrentConstantCalls
@barbatos2011 barbatos2011 force-pushed the fix/api-input-validation branch from 968db25 to d2d0c03 Compare April 2, 2026 02:57
- node.http.maxRequestBodySize: 5MB default
- node.jsonrpc: maxBatchSize, maxResponseSize, maxRequestTimeout,
  maxAddressSize with geth-aligned defaults
- rate.limiter.http: recommended QPS for TriggerConstantContractServlet
  and EstimateEnergyServlet
- global.qps / global.ip.qps: recommended values in comments
- vm.maxEnergyLimitForConstant / maxConcurrentConstantCalls: recommended
  values in comments
@barbatos2011 barbatos2011 force-pushed the fix/api-input-validation branch from d2d0c03 to 5a723c3 Compare April 2, 2026 04:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant