Skip to content

Validate TxRep integer widths and write uint64 values unsigned - #195

Merged
christian-rogobete merged 1 commit into
mainfrom
hardening-txrep-numbers
Nov 3, 2026
Merged

christian-rogobete merged 1 commit into
mainfrom
hardening-txrep-numbers

Conversation

@christian-rogobete

Copy link
Copy Markdown
Member

Hardens the TxRep parser against integers outside their XDR types and fixes the text of large uint64 values. Before this change, TxRep::transactionEnvelopeXdrBase64FromTxRep read every integer with intval, a cast or BigInteger after stripping one minus sign and checked no width. A uint32 field outside its range encoded its low 32 bits: tx.fee: 4294967296 gave a fee of 0 and tx.fee: -1 a fee of 4294967295. A uint64 text at or above 2^63 saturated to 9223372036854775807, tx.seqNum: 9223372036854775808 passed, a negative array length gave an empty array, 0xZZ read as 0, --3 as 3, 1e2 as 100, and a BigInteger field read +5 as 0. On the writing side a uint64 at or above 2^63 was printed as a negative number. A malformed token threw without the field key.

This change:

  • Adds the keyed readers parseInt32, parseUint32, parseInt64 and parseUint64 to TxRepHelper, with one token grammar (an optional sign, then decimal digits or 0x hex digits) and a range check per width. parseUint64 stores a value at or above 2^63 in the two's-complement form the XDR layer uses. parseInt and parseBigInt share the grammar and keep their signatures.
  • Makes the generator choose the reader from the XDR type of every field, array length, typedef, union discriminant and enum ordinal, and print uint64 fields as unsigned decimals. Regenerates the 87 affected XDR classes and the three generator snapshots.
  • Replaces a secret seed in the text of these messages with [redacted seed].
  • Reads the fee, the fee bump fee, the operation and signature counts, the extension version and the memo id of the hand-written envelope parser through the typed readers. The seven hand-written overrides of the time bounds and liquidity pool operations use them too.
  • Adds tests for malformed tokens, the four widths at their bounds, uint64 round trips at 2^63 and 2^64-1, enum ordinals, negative and oversized lengths, a leading + and the seed redaction. Further tests pin the keyed messages of the facade and the fee bump parser. A generator test pins the reader of five representative fields and that no generated class calls the unkeyed parsers.

Every case throws InvalidArgumentException with the key and the text; valid TxReps parse as before. The unit suite, PHPStan, the generator tests and the XDR validation pass, and a second regeneration changes no file. A replay of the 41 TxRep texts and 622 integer tokens of the unit suite gives identical bytes and values on both versions. Patch coverage is 232 of 235 changed production lines; the three uncovered lines read signatures.len in the generated envelope parsers, which no caller reaches because the facade parses signatures itself.

Closes #194

Hardens the TxRep parser against integers outside their XDR types and
fixes the text of large `uint64` values. Before this change,
`TxRep::transactionEnvelopeXdrBase64FromTxRep` read every integer with
`intval`, a cast or `BigInteger` after stripping one minus sign and
checked no width. A `uint32` field outside its range encoded its low 32
bits: `tx.fee: 4294967296` gave a fee of 0 and `tx.fee: -1` a fee of
4294967295. A `uint64` text at or above 2^63 saturated to
9223372036854775807, `tx.seqNum: 9223372036854775808` passed, a negative
array length gave an empty array, `0xZZ` read as 0, `--3` as 3, `1e2` as
100, and a `BigInteger` field read `+5` as 0. On the writing side a
`uint64` at or above 2^63 was printed as a negative number. A malformed
token threw without the field key.

This change:

- Adds the keyed readers `parseInt32`, `parseUint32`, `parseInt64` and
  `parseUint64` to `TxRepHelper`, with one token grammar (an optional
  sign, then decimal digits or `0x` hex digits) and a range check per
  width. `parseUint64` stores a value at or above 2^63 in the
  two's-complement form the XDR layer uses. `parseInt` and `parseBigInt`
  share the grammar and keep their signatures.
- Makes the generator choose the reader from the XDR type of every
  field, array length, typedef, union discriminant and enum ordinal, and
  print `uint64` fields as unsigned decimals. Regenerates the 87
  affected XDR classes and the three generator snapshots.
- Replaces a secret seed in the text of these messages with `[redacted
  seed]`.
- Reads the fee, the fee bump fee, the operation and signature counts,
  the extension version and the memo id of the hand-written envelope
  parser through the typed readers. The seven hand-written overrides of
  the time bounds and liquidity pool operations use them too.
- Adds tests for malformed tokens, the four widths at their bounds,
  `uint64` round trips at 2^63 and 2^64-1, enum ordinals, negative and
  oversized lengths, a leading `+` and the seed redaction. Further tests
  pin the keyed messages of the facade and the fee bump parser. A
  generator test pins the reader of five representative fields and that
  no generated class calls the unkeyed parsers.

Every case throws `InvalidArgumentException` with the key and the text;
valid TxReps parse as before. The unit suite, PHPStan, the generator
tests and the XDR validation pass, and a second regeneration changes no
file. A replay of the 41 TxRep texts and 622 integer tokens of the unit
suite gives identical bytes and values on both versions. Patch coverage
is 232 of 235 changed production lines; the three uncovered lines read
`signatures.len` in the generated envelope parsers, which no caller
reaches because the facade parses signatures itself.

Closes #194
@codecov

codecov Bot commented Nov 3, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 98.76033% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 93.45%. Comparing base (55f7461) to head (afc63c8).

Files with missing lines Patch % Lines
...o/StellarSDK/Xdr/XdrFeeBumpTransactionEnvelope.php 0.00% 1 Missing ⚠️
Soneso/StellarSDK/Xdr/XdrTransactionV0Envelope.php 0.00% 1 Missing ⚠️
Soneso/StellarSDK/Xdr/XdrTransactionV1Envelope.php 0.00% 1 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@             Coverage Diff              @@
##               main     #195      +/-   ##
============================================
+ Coverage     93.41%   93.45%   +0.04%     
- Complexity    21578    21602      +24     
============================================
  Files          1006     1006              
  Lines         52310    52362      +52     
============================================
+ Hits          48866    48937      +71     
+ Misses         3444     3425      -19     
Files with missing lines Coverage Δ
Soneso/StellarSDK/SEP/TxRep/TxRep.php 80.48% <100.00%> (+1.95%) ⬆️
Soneso/StellarSDK/Xdr/TxRepHelper.php 97.33% <100.00%> (+0.01%) ⬆️
Soneso/StellarSDK/Xdr/XdrAllowTrustOperation.php 97.05% <100.00%> (ø)
Soneso/StellarSDK/Xdr/XdrAssetType.php 97.64% <100.00%> (+0.05%) ⬆️
...eso/StellarSDK/Xdr/XdrChangeTrustOperationBase.php 96.42% <100.00%> (ø)
Soneso/StellarSDK/Xdr/XdrClaimPredicate.php 95.97% <100.00%> (ø)
Soneso/StellarSDK/Xdr/XdrClaimPredicateType.php 98.01% <100.00%> (+0.04%) ⬆️
...oneso/StellarSDK/Xdr/XdrClaimableBalanceIDType.php 96.61% <100.00%> (+0.11%) ⬆️
Soneso/StellarSDK/Xdr/XdrClaimantType.php 95.16% <100.00%> (+0.16%) ⬆️
Soneso/StellarSDK/Xdr/XdrClawbackOperation.php 97.05% <100.00%> (ø)
... and 79 more

... and 1 file with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@christian-rogobete
christian-rogobete merged commit de21bcc into main Nov 3, 2026
13 checks passed
@christian-rogobete
christian-rogobete deleted the hardening-txrep-numbers branch November 3, 2026 15:36
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.

TxRep parsing truncates out-of-range integers, accepts negative lengths and writes uint64 values signed

1 participant