From cf42b533f192192b81295f6f32fa17488b49f63d Mon Sep 17 00:00:00 2001 From: Jun Luo <4catcode@gmail.com> Date: Sat, 31 Jan 2026 07:31:45 +0800 Subject: [PATCH] fix: add overflow check in `TimeBounds.expiresAfter()` to prevent integer overflow when timeout is too large. --- CHANGELOG.md | 1 + src/main/java/org/stellar/sdk/TimeBounds.java | 7 ++++++ .../kotlin/org/stellar/sdk/TimeBoundsTest.kt | 22 +++++++++++++++++++ 3 files changed, 30 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 73888994a..21246ff17 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### Update - fix: add stricter validation for Ed25519 Signed Payload. - fix: replace assert statements with explicit null checks in `Federation` class to ensure validation is not bypassed when assertions are disabled. +- fix: add overflow check in `TimeBounds.expiresAfter()` to prevent integer overflow when timeout is too large. ## 2.2.1 diff --git a/src/main/java/org/stellar/sdk/TimeBounds.java b/src/main/java/org/stellar/sdk/TimeBounds.java index 5b3610b24..662e2cf8f 100644 --- a/src/main/java/org/stellar/sdk/TimeBounds.java +++ b/src/main/java/org/stellar/sdk/TimeBounds.java @@ -63,9 +63,16 @@ public TimeBounds(long minTime, long maxTime) { * * @param timeout Timeout in seconds. * @return TimeBounds + * @throws IllegalArgumentException if timeout is negative or would cause overflow */ public static TimeBounds expiresAfter(long timeout) { + if (timeout < 0) { + throw new IllegalArgumentException("timeout cannot be negative"); + } long now = System.currentTimeMillis() / 1000L; + if (timeout > Long.MAX_VALUE - now) { + throw new IllegalArgumentException("timeout is too large, would cause overflow"); + } long endTime = now + timeout; return new TimeBounds(0, endTime); } diff --git a/src/test/kotlin/org/stellar/sdk/TimeBoundsTest.kt b/src/test/kotlin/org/stellar/sdk/TimeBoundsTest.kt index c4d3a6143..c903c43ae 100644 --- a/src/test/kotlin/org/stellar/sdk/TimeBoundsTest.kt +++ b/src/test/kotlin/org/stellar/sdk/TimeBoundsTest.kt @@ -60,6 +60,28 @@ class TimeBoundsTest : actualMaxTime shouldBeInRange (now + timeout - 1)..(now + timeout + 1) } + test("expiresAfter should throw IllegalArgumentException for negative timeout") { + val exception = shouldThrow { TimeBounds.expiresAfter(-1) } + exception.message shouldBe "timeout cannot be negative" + } + + test("expiresAfter should throw IllegalArgumentException for overflow") { + val exception = + shouldThrow { TimeBounds.expiresAfter(Long.MAX_VALUE) } + exception.message shouldBe "timeout is too large, would cause overflow" + } + + test("expiresAfter should handle large valid timeout") { + // 100 years in seconds + val hundredYearsInSeconds = 100L * 365 * 24 * 60 * 60 + val timeBounds = TimeBounds.expiresAfter(hundredYearsInSeconds) + val now = System.currentTimeMillis() / 1000L + + timeBounds.minTime.toLong() shouldBe 0 + timeBounds.maxTime.toLong() shouldBeInRange + (now + hundredYearsInSeconds - 1)..(now + hundredYearsInSeconds + 1) + } + test("should handle large time values") { val largeTime = Long.MAX_VALUE - 1 val timeBounds = TimeBounds(0, largeTime)