Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,18 @@
- fix: use `StandardCharsets.UTF_8` explicitly when converting byte arrays to strings to ensure consistent behavior across different platforms.
- refactor: use static initialization for `GsonSingleton` to ensure thread safety.
- fix: use `commons-codec` for hex encoding/decoding in `Util` class to properly validate input and throw clear exceptions for invalid hex strings.
- fix: improve XDR decoding security and correctness.
- Add decoding depth limit to prevent stack overflow (default: 200)
- Add input length tracking to prevent DoS via oversized allocations
- Validate variable-length array/opaque/string sizes before allocation
- Validate variable-length types don't exceed declared max size
- Validate fixed-length opaque/array sizes match declared size
- Fix short read handling for opaque/string with proper padding
- Remove incorrect auto-padding from read(byte[], int, int)
- Reject unknown union discriminant values when no default arm
- Validate boolean/optional flags are strictly 0 or 1 per RFC 4506
- Fix EOF handling in single-byte read
- Deprecate unsafe readIntArray/readFloatArray/readDoubleArray methods

## 2.2.1

Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ xdr/Stellar-contract-config-setting.x \
xdr/Stellar-exporter.x

# xdrgen commit to use, see https://github.com/stellar/xdrgen
XDRGEN_COMMIT=6b98787dcbb2b6407880adec5f74c6d88975ab0f
XDRGEN_COMMIT=621d042b824f67ac65cc53d0e5e381e24aed4583
# stellar-xdr commit to use, see https://github.com/stellar/stellar-xdr
XDR_COMMIT=4b7a2ef7931ab2ca2499be68d849f38190b443ca

Expand Down
11 changes: 3 additions & 8 deletions src/main/java/org/stellar/sdk/StrKey.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package org.stellar.sdk;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigInteger;
Expand All @@ -18,7 +17,6 @@
import org.stellar.sdk.xdr.PublicKeyType;
import org.stellar.sdk.xdr.Uint256;
import org.stellar.sdk.xdr.Uint64;
import org.stellar.sdk.xdr.XdrDataInputStream;
import org.stellar.sdk.xdr.XdrUnsignedHyperInteger;

/**
Expand Down Expand Up @@ -457,15 +455,12 @@ public static MuxedAccount encodeToXDRMuxedAccount(String data) {
}
break;
case MED25519_PUBLIC_KEY:
XdrDataInputStream input =
new XdrDataInputStream(
new ByteArrayInputStream(
decodeCheck(VersionByte.MED25519_PUBLIC_KEY, data.toCharArray())));
byte[] input = decodeCheck(VersionByte.MED25519_PUBLIC_KEY, data.toCharArray());
muxed.setDiscriminant(CryptoKeyType.KEY_TYPE_MUXED_ED25519);
MuxedAccount.MuxedAccountMed25519 med = new MuxedAccount.MuxedAccountMed25519();
try {
med.setEd25519(Uint256.decode(input));
med.setId(new Uint64(XdrUnsignedHyperInteger.decode(input)));
med.setEd25519(Uint256.fromXdrByteArray(Arrays.copyOfRange(input, 0, 32)));
med.setId(Uint64.fromXdrByteArray(Arrays.copyOfRange(input, 32, 40)));
} catch (IOException e) {
throw new IllegalArgumentException("invalid address: " + data, e);
}
Expand Down
68 changes: 53 additions & 15 deletions src/main/java/org/stellar/sdk/xdr/AccountEntry.java
Original file line number Diff line number Diff line change
Expand Up @@ -76,35 +76,60 @@ public void encode(XdrDataOutputStream stream) throws IOException {
homeDomain.encode(stream);
thresholds.encode(stream);
int signersSize = getSigners().length;
if (signersSize > 20) {
throw new IOException("signers size " + signersSize + " exceeds max size 20");
}
stream.writeInt(signersSize);
for (int i = 0; i < signersSize; i++) {
signers[i].encode(stream);
}
ext.encode(stream);
}

public static AccountEntry decode(XdrDataInputStream stream) throws IOException {
public static AccountEntry decode(XdrDataInputStream stream, int maxDepth) throws IOException {
if (maxDepth <= 0) {
throw new IOException("Maximum decoding depth reached");
}
maxDepth -= 1;
AccountEntry decodedAccountEntry = new AccountEntry();
decodedAccountEntry.accountID = AccountID.decode(stream);
decodedAccountEntry.balance = Int64.decode(stream);
decodedAccountEntry.seqNum = SequenceNumber.decode(stream);
decodedAccountEntry.numSubEntries = Uint32.decode(stream);
int inflationDestPresent = stream.readInt();
if (inflationDestPresent != 0) {
decodedAccountEntry.inflationDest = AccountID.decode(stream);
decodedAccountEntry.accountID = AccountID.decode(stream, maxDepth);
decodedAccountEntry.balance = Int64.decode(stream, maxDepth);
decodedAccountEntry.seqNum = SequenceNumber.decode(stream, maxDepth);
decodedAccountEntry.numSubEntries = Uint32.decode(stream, maxDepth);
boolean inflationDestPresent = stream.readXdrBoolean();
if (inflationDestPresent) {
decodedAccountEntry.inflationDest = AccountID.decode(stream, maxDepth);
}
decodedAccountEntry.flags = Uint32.decode(stream);
decodedAccountEntry.homeDomain = String32.decode(stream);
decodedAccountEntry.thresholds = Thresholds.decode(stream);
decodedAccountEntry.flags = Uint32.decode(stream, maxDepth);
decodedAccountEntry.homeDomain = String32.decode(stream, maxDepth);
decodedAccountEntry.thresholds = Thresholds.decode(stream, maxDepth);
int signersSize = stream.readInt();
if (signersSize < 0) {
throw new IOException("signers size " + signersSize + " is negative");
}
if (signersSize > 20) {
throw new IOException("signers size " + signersSize + " exceeds max size 20");
}
int signersRemainingInputLen = stream.getRemainingInputLen();
if (signersRemainingInputLen >= 0 && signersRemainingInputLen < signersSize) {
throw new IOException(
"signers size "
+ signersSize
+ " exceeds remaining input length "
+ signersRemainingInputLen);
}
decodedAccountEntry.signers = new Signer[signersSize];
for (int i = 0; i < signersSize; i++) {
decodedAccountEntry.signers[i] = Signer.decode(stream);
decodedAccountEntry.signers[i] = Signer.decode(stream, maxDepth);
}
decodedAccountEntry.ext = AccountEntryExt.decode(stream);
decodedAccountEntry.ext = AccountEntryExt.decode(stream, maxDepth);
return decodedAccountEntry;
}

public static AccountEntry decode(XdrDataInputStream stream) throws IOException {
return decode(stream, XdrDataInputStream.DEFAULT_MAX_DEPTH);
}

public static AccountEntry fromXdrBase64(String xdr) throws IOException {
byte[] bytes = Base64Factory.getInstance().decode(xdr);
return fromXdrByteArray(bytes);
Expand All @@ -113,6 +138,7 @@ public static AccountEntry fromXdrBase64(String xdr) throws IOException {
public static AccountEntry fromXdrByteArray(byte[] xdr) throws IOException {
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(xdr);
XdrDataInputStream xdrDataInputStream = new XdrDataInputStream(byteArrayInputStream);
xdrDataInputStream.setMaxInputLen(xdr.length);
return decode(xdrDataInputStream);
}

Expand Down Expand Up @@ -148,20 +174,31 @@ public void encode(XdrDataOutputStream stream) throws IOException {
}
}

public static AccountEntryExt decode(XdrDataInputStream stream) throws IOException {
public static AccountEntryExt decode(XdrDataInputStream stream, int maxDepth)
throws IOException {
if (maxDepth <= 0) {
throw new IOException("Maximum decoding depth reached");
}
maxDepth -= 1;
AccountEntryExt decodedAccountEntryExt = new AccountEntryExt();
Integer discriminant = stream.readInt();
decodedAccountEntryExt.setDiscriminant(discriminant);
switch (decodedAccountEntryExt.getDiscriminant()) {
case 0:
break;
case 1:
decodedAccountEntryExt.v1 = AccountEntryExtensionV1.decode(stream);
decodedAccountEntryExt.v1 = AccountEntryExtensionV1.decode(stream, maxDepth);
break;
default:
throw new IOException("Unknown discriminant value: " + discriminant);
}
return decodedAccountEntryExt;
}

public static AccountEntryExt decode(XdrDataInputStream stream) throws IOException {
return decode(stream, XdrDataInputStream.DEFAULT_MAX_DEPTH);
}

public static AccountEntryExt fromXdrBase64(String xdr) throws IOException {
byte[] bytes = Base64Factory.getInstance().decode(xdr);
return fromXdrByteArray(bytes);
Expand All @@ -170,6 +207,7 @@ public static AccountEntryExt fromXdrBase64(String xdr) throws IOException {
public static AccountEntryExt fromXdrByteArray(byte[] xdr) throws IOException {
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(xdr);
XdrDataInputStream xdrDataInputStream = new XdrDataInputStream(byteArrayInputStream);
xdrDataInputStream.setMaxInputLen(xdr.length);
return decode(xdrDataInputStream);
}
}
Expand Down
32 changes: 27 additions & 5 deletions src/main/java/org/stellar/sdk/xdr/AccountEntryExtensionV1.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,22 @@ public void encode(XdrDataOutputStream stream) throws IOException {
ext.encode(stream);
}

public static AccountEntryExtensionV1 decode(XdrDataInputStream stream) throws IOException {
public static AccountEntryExtensionV1 decode(XdrDataInputStream stream, int maxDepth)
throws IOException {
if (maxDepth <= 0) {
throw new IOException("Maximum decoding depth reached");
}
maxDepth -= 1;
AccountEntryExtensionV1 decodedAccountEntryExtensionV1 = new AccountEntryExtensionV1();
decodedAccountEntryExtensionV1.liabilities = Liabilities.decode(stream);
decodedAccountEntryExtensionV1.ext = AccountEntryExtensionV1Ext.decode(stream);
decodedAccountEntryExtensionV1.liabilities = Liabilities.decode(stream, maxDepth);
decodedAccountEntryExtensionV1.ext = AccountEntryExtensionV1Ext.decode(stream, maxDepth);
return decodedAccountEntryExtensionV1;
}

public static AccountEntryExtensionV1 decode(XdrDataInputStream stream) throws IOException {
return decode(stream, XdrDataInputStream.DEFAULT_MAX_DEPTH);
}

public static AccountEntryExtensionV1 fromXdrBase64(String xdr) throws IOException {
byte[] bytes = Base64Factory.getInstance().decode(xdr);
return fromXdrByteArray(bytes);
Expand All @@ -58,6 +67,7 @@ public static AccountEntryExtensionV1 fromXdrBase64(String xdr) throws IOExcepti
public static AccountEntryExtensionV1 fromXdrByteArray(byte[] xdr) throws IOException {
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(xdr);
XdrDataInputStream xdrDataInputStream = new XdrDataInputStream(byteArrayInputStream);
xdrDataInputStream.setMaxInputLen(xdr.length);
return decode(xdrDataInputStream);
}

Expand Down Expand Up @@ -93,7 +103,12 @@ public void encode(XdrDataOutputStream stream) throws IOException {
}
}

public static AccountEntryExtensionV1Ext decode(XdrDataInputStream stream) throws IOException {
public static AccountEntryExtensionV1Ext decode(XdrDataInputStream stream, int maxDepth)
throws IOException {
if (maxDepth <= 0) {
throw new IOException("Maximum decoding depth reached");
}
maxDepth -= 1;
AccountEntryExtensionV1Ext decodedAccountEntryExtensionV1Ext =
new AccountEntryExtensionV1Ext();
Integer discriminant = stream.readInt();
Expand All @@ -102,12 +117,18 @@ public static AccountEntryExtensionV1Ext decode(XdrDataInputStream stream) throw
case 0:
break;
case 2:
decodedAccountEntryExtensionV1Ext.v2 = AccountEntryExtensionV2.decode(stream);
decodedAccountEntryExtensionV1Ext.v2 = AccountEntryExtensionV2.decode(stream, maxDepth);
break;
default:
throw new IOException("Unknown discriminant value: " + discriminant);
}
return decodedAccountEntryExtensionV1Ext;
}

public static AccountEntryExtensionV1Ext decode(XdrDataInputStream stream) throws IOException {
return decode(stream, XdrDataInputStream.DEFAULT_MAX_DEPTH);
}

public static AccountEntryExtensionV1Ext fromXdrBase64(String xdr) throws IOException {
byte[] bytes = Base64Factory.getInstance().decode(xdr);
return fromXdrByteArray(bytes);
Expand All @@ -116,6 +137,7 @@ public static AccountEntryExtensionV1Ext fromXdrBase64(String xdr) throws IOExce
public static AccountEntryExtensionV1Ext fromXdrByteArray(byte[] xdr) throws IOException {
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(xdr);
XdrDataInputStream xdrDataInputStream = new XdrDataInputStream(byteArrayInputStream);
xdrDataInputStream.setMaxInputLen(xdr.length);
return decode(xdrDataInputStream);
}
}
Expand Down
57 changes: 50 additions & 7 deletions src/main/java/org/stellar/sdk/xdr/AccountEntryExtensionV2.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,27 +46,57 @@ public void encode(XdrDataOutputStream stream) throws IOException {
numSponsored.encode(stream);
numSponsoring.encode(stream);
int signerSponsoringIDsSize = getSignerSponsoringIDs().length;
if (signerSponsoringIDsSize > 20) {
throw new IOException(
"signerSponsoringIDs size " + signerSponsoringIDsSize + " exceeds max size 20");
}
stream.writeInt(signerSponsoringIDsSize);
for (int i = 0; i < signerSponsoringIDsSize; i++) {
signerSponsoringIDs[i].encode(stream);
}
ext.encode(stream);
}

public static AccountEntryExtensionV2 decode(XdrDataInputStream stream) throws IOException {
public static AccountEntryExtensionV2 decode(XdrDataInputStream stream, int maxDepth)
throws IOException {
if (maxDepth <= 0) {
throw new IOException("Maximum decoding depth reached");
}
maxDepth -= 1;
AccountEntryExtensionV2 decodedAccountEntryExtensionV2 = new AccountEntryExtensionV2();
decodedAccountEntryExtensionV2.numSponsored = Uint32.decode(stream);
decodedAccountEntryExtensionV2.numSponsoring = Uint32.decode(stream);
decodedAccountEntryExtensionV2.numSponsored = Uint32.decode(stream, maxDepth);
decodedAccountEntryExtensionV2.numSponsoring = Uint32.decode(stream, maxDepth);
int signerSponsoringIDsSize = stream.readInt();
if (signerSponsoringIDsSize < 0) {
throw new IOException("signerSponsoringIDs size " + signerSponsoringIDsSize + " is negative");
}
if (signerSponsoringIDsSize > 20) {
throw new IOException(
"signerSponsoringIDs size " + signerSponsoringIDsSize + " exceeds max size 20");
}
int signerSponsoringIDsRemainingInputLen = stream.getRemainingInputLen();
if (signerSponsoringIDsRemainingInputLen >= 0
&& signerSponsoringIDsRemainingInputLen < signerSponsoringIDsSize) {
throw new IOException(
"signerSponsoringIDs size "
+ signerSponsoringIDsSize
+ " exceeds remaining input length "
+ signerSponsoringIDsRemainingInputLen);
}
decodedAccountEntryExtensionV2.signerSponsoringIDs =
new SponsorshipDescriptor[signerSponsoringIDsSize];
for (int i = 0; i < signerSponsoringIDsSize; i++) {
decodedAccountEntryExtensionV2.signerSponsoringIDs[i] = SponsorshipDescriptor.decode(stream);
decodedAccountEntryExtensionV2.signerSponsoringIDs[i] =
SponsorshipDescriptor.decode(stream, maxDepth);
}
decodedAccountEntryExtensionV2.ext = AccountEntryExtensionV2Ext.decode(stream);
decodedAccountEntryExtensionV2.ext = AccountEntryExtensionV2Ext.decode(stream, maxDepth);
return decodedAccountEntryExtensionV2;
}

public static AccountEntryExtensionV2 decode(XdrDataInputStream stream) throws IOException {
return decode(stream, XdrDataInputStream.DEFAULT_MAX_DEPTH);
}

public static AccountEntryExtensionV2 fromXdrBase64(String xdr) throws IOException {
byte[] bytes = Base64Factory.getInstance().decode(xdr);
return fromXdrByteArray(bytes);
Expand All @@ -75,6 +105,7 @@ public static AccountEntryExtensionV2 fromXdrBase64(String xdr) throws IOExcepti
public static AccountEntryExtensionV2 fromXdrByteArray(byte[] xdr) throws IOException {
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(xdr);
XdrDataInputStream xdrDataInputStream = new XdrDataInputStream(byteArrayInputStream);
xdrDataInputStream.setMaxInputLen(xdr.length);
return decode(xdrDataInputStream);
}

Expand Down Expand Up @@ -110,7 +141,12 @@ public void encode(XdrDataOutputStream stream) throws IOException {
}
}

public static AccountEntryExtensionV2Ext decode(XdrDataInputStream stream) throws IOException {
public static AccountEntryExtensionV2Ext decode(XdrDataInputStream stream, int maxDepth)
throws IOException {
if (maxDepth <= 0) {
throw new IOException("Maximum decoding depth reached");
}
maxDepth -= 1;
AccountEntryExtensionV2Ext decodedAccountEntryExtensionV2Ext =
new AccountEntryExtensionV2Ext();
Integer discriminant = stream.readInt();
Expand All @@ -119,12 +155,18 @@ public static AccountEntryExtensionV2Ext decode(XdrDataInputStream stream) throw
case 0:
break;
case 3:
decodedAccountEntryExtensionV2Ext.v3 = AccountEntryExtensionV3.decode(stream);
decodedAccountEntryExtensionV2Ext.v3 = AccountEntryExtensionV3.decode(stream, maxDepth);
break;
default:
throw new IOException("Unknown discriminant value: " + discriminant);
}
return decodedAccountEntryExtensionV2Ext;
}

public static AccountEntryExtensionV2Ext decode(XdrDataInputStream stream) throws IOException {
return decode(stream, XdrDataInputStream.DEFAULT_MAX_DEPTH);
}

public static AccountEntryExtensionV2Ext fromXdrBase64(String xdr) throws IOException {
byte[] bytes = Base64Factory.getInstance().decode(xdr);
return fromXdrByteArray(bytes);
Expand All @@ -133,6 +175,7 @@ public static AccountEntryExtensionV2Ext fromXdrBase64(String xdr) throws IOExce
public static AccountEntryExtensionV2Ext fromXdrByteArray(byte[] xdr) throws IOException {
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(xdr);
XdrDataInputStream xdrDataInputStream = new XdrDataInputStream(byteArrayInputStream);
xdrDataInputStream.setMaxInputLen(xdr.length);
return decode(xdrDataInputStream);
}
}
Expand Down
Loading
Loading