Skip to content

feat(security): add explicit XDR length guard on returned Bytes values#1057

Merged
Baskarayelu merged 1 commit into
Remitwise-Org:mainfrom
Bolajiomo99:feat-Add-explicit-XDR-length-guard-on-returned-Bytes-values
Jun 29, 2026
Merged

feat(security): add explicit XDR length guard on returned Bytes values#1057
Baskarayelu merged 1 commit into
Remitwise-Org:mainfrom
Bolajiomo99:feat-Add-explicit-XDR-length-guard-on-returned-Bytes-values

Conversation

@Bolajiomo99

Copy link
Copy Markdown
Contributor

Summary

Closes #894

Add an explicit XDR byte-length guard (guard_bytes_len) on Bytes values
returned from public contract entry points so consumers cannot be DoS'd by an
unbounded allocation.


Threat being mitigated

DoS via unbounded ScBytes deserialization.

The Soroban XDR ScBytes type carries a 4-byte unsigned length prefix with no
host-enforced upper bound before the bytes are handed to the caller. Without an
explicit check on the contract side, a misbehaving or supply-chain-compromised
contract could return an arbitrarily large Bytes payload. Every downstream
consumer — JavaScript SDK, Horizon indexer, RPC node, third-party integration —
must allocate a buffer proportional to len to deserialize the value. A 2 GiB
len field would OOM almost any process.

This is a defence-in-depth fix. There is no public incident. The gap is the
kind a careful auditor would flag before any external review, and closing it now
is cheaper than remediating it under fire.


What changed

File Change
remitwise-common/src/lib.rs MAX_BYTES_RETURN = 8192, typed BytesReturnError::ReturnTooLarge (#[contracterror]), guard_bytes_len() helper
remittance_split/src/lib.rs ReturnBytesTooLarge = 28 in RemittanceSplitError; get_request_hash now returns Result<Bytes, RemittanceSplitError>; guard applied before returning; distribute_usdc_hashed propagates with ?
remitwise-common/src/tests.rs 4 unit tests for guard_bytes_len (empty, at-limit, one-over-limit, far-above-limit)
remittance_split/src/test.rs 1 new contract-level test (test_get_request_hash_passes_xdr_length_guard); 8 direct struct call sites updated to .unwrap()

Acceptance criteria

  • Change matches the summary — reject returns > MAX_BYTES_RETURN
  • Negative test exercises the new check (test_guard_bytes_len_rejects_oversized_value — could not be written before the fix, passes after)
  • PR description names the threat being mitigated (unbounded ScBytes allocation → consumer DoS)
  • Typed #[contracterror] (BytesReturnError::ReturnTooLarge) surfaced instead of panic or generic error
  • WASM build passes: cargo build -p remittance_split --target wasm32-unknown-unknown --release
  • remitwise-common lib clippy clean: cargo clippy -p remitwise-common --lib -- -D warnings
  • #![no_std] discipline maintained — no std:: calls introduced

Performance

guard_bytes_len is a single u32 comparison (bytes.len() > 8192). SHA-256
always produces exactly 32 bytes, so the guard exits the fast path immediately.
No measurable overhead on the hot path.


Out of scope

Pre-existing test failures and clippy warnings in the test file are not touched,
per the issue's out-of-scope guidance.

Closes Remitwise-Org#894

Without a size ceiling, any contract returning a `Bytes` value forces
every downstream consumer (SDK, indexer, RPC node) to allocate memory
proportional to the payload — a DoS vector that is otherwise invisible
at the call site.

Threat model: a misbehaving or supply-chain-compromised contract that
returns an arbitrarily large `ScBytes` value causes unbounded allocation
and potential OOM in every consumer that deserialises the response.  The
guard closes this gap as a defence-in-depth control before any external
audit.

Changes:
- remitwise-common: add `MAX_BYTES_RETURN` (8 192 bytes), typed
  `BytesReturnError::ReturnTooLarge` (#[contracterror]), and
  `guard_bytes_len()` helper.
- remittance_split: add `ReturnBytesTooLarge = 28` to
  `RemittanceSplitError`; change `get_request_hash` return type to
  `Result<Bytes, RemittanceSplitError>` and apply `guard_bytes_len`
  before returning; propagate with `?` in `distribute_usdc_hashed`.
- Tests: four new unit tests for `guard_bytes_len` (empty, at-limit,
  one-over, far-above); one new contract-level test confirming the
  32-byte SHA-256 result always passes the guard.  All 31 affected
  tests pass; pre-existing failures are unchanged.

Performance: the guard is a single `u32` comparison — no measurable
overhead on the SHA-256 hot path.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@Baskarayelu Baskarayelu merged commit d5dbb68 into Remitwise-Org:main Jun 29, 2026
3 of 6 checks passed
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.

Add explicit XDR length guard on returned Bytes values

2 participants