This is an automated email from the ASF dual-hosted git repository. ggregory pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/commons-io.git
commit 2dcd1ed28b976eb4d1ddbb1775255196696a5fd2 Author: Gary Gregory <garydgreg...@gmail.com> AuthorDate: Mon Mar 31 14:13:36 2025 -0400 FileTimes.toNtfsTime(*) methods can overflow result values --- src/changes/changes.xml | 1 + .../commons/io/file/attribute/FileTimes.java | 20 ++++++++-- .../commons/io/file/attribute/FileTimesTest.java | 45 ++++++++++++++++++++-- 3 files changed, 58 insertions(+), 8 deletions(-) diff --git a/src/changes/changes.xml b/src/changes/changes.xml index c26a92878..c592022cc 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -69,6 +69,7 @@ The <action> type attribute can be add,update,fix,remove. <action dev="ggregory" type="fix" issue="IO-871" due-to="Éamonn McManus, Gary Gregory">IOUtils.contentEquals is incorrect when InputStream.available under-reports.</action> <action dev="ggregory" type="fix" issue="IO-873" due-to="Gary Gregory">java.lang.ArithmeticException: long overflow java.lang.Math.addExact(Math.java:932) at org.apache.commons.io.file.attribute.FileTimes.ntfsTimeToFileTime(FileTimes.java:164). See also https://issues.apache.org/jira/browse/MDEP-978.</action> <action dev="ggregory" type="fix" issue="IO-873" due-to="Gary Gregory">java.lang.ArithmeticException: long overflow java.lang.Math.addExact(Math.java:932) at org.apache.commons.io.file.attribute.FileTimes.ntfsTimeToDate(long).</action> + <action dev="ggregory" type="fix" due-to="Gary Gregory">FileTimes.toNtfsTime(*) methods can overflow result values.</action> <!-- ADD --> <action dev="ggregory" type="add" issue="IO-860" due-to="Nico Strecker, Gary Gregory">Add ThrottledInputStream.Builder.setMaxBytes(long, ChronoUnit).</action> <action dev="ggregory" type="add" due-to="Gary Gregory">Add IOIterable.</action> diff --git a/src/main/java/org/apache/commons/io/file/attribute/FileTimes.java b/src/main/java/org/apache/commons/io/file/attribute/FileTimes.java index 3d54b5116..d811c54e4 100644 --- a/src/main/java/org/apache/commons/io/file/attribute/FileTimes.java +++ b/src/main/java/org/apache/commons/io/file/attribute/FileTimes.java @@ -40,6 +40,10 @@ */ public final class FileTimes { + private static final BigDecimal LONG_MIN_VALUE_BD = BigDecimal.valueOf(Long.MIN_VALUE); + + private static final BigDecimal LONG_MAX_VALUE_BD = BigDecimal.valueOf(Long.MAX_VALUE); + private static final MathContext MATH_CONTEXT = new MathContext(0, RoundingMode.FLOOR); /** @@ -74,6 +78,8 @@ public final class FileTimes { */ static final long HUNDRED_NANOS_PER_MILLISECOND = TimeUnit.MILLISECONDS.toNanos(1) / 100; + static final BigDecimal HUNDRED_NANOS_PER_MILLISECOND_BD = BigDecimal.valueOf(HUNDRED_NANOS_PER_MILLISECOND); + private static final long HUNDRED = 100L; private static final BigDecimal HUNDRED_BD = BigDecimal.valueOf(HUNDRED); @@ -274,8 +280,7 @@ public static FileTime toFileTime(final Date date) { * @return the NTFS time, 100-nanosecond units since 1 January 1601. */ public static long toNtfsTime(final Date date) { - final long javaHundredNanos = date.getTime() * HUNDRED_NANOS_PER_MILLISECOND; - return Math.subtractExact(javaHundredNanos, UNIX_TO_NTFS_OFFSET); + return toNtfsTime(date.getTime()); } /** @@ -308,8 +313,15 @@ static long toNtfsTime(final Instant instant) { * @since 2.16.0 */ public static long toNtfsTime(final long javaTime) { - final long javaHundredNanos = javaTime * HUNDRED_NANOS_PER_MILLISECOND; - return Math.subtractExact(javaHundredNanos, UNIX_TO_NTFS_OFFSET); + final BigDecimal javaHundredNanos = BigDecimal.valueOf(javaTime).multiply(HUNDRED_NANOS_PER_MILLISECOND_BD); + final BigDecimal ntfsTime = javaHundredNanos.subtract(UNIX_TO_NTFS_OFFSET_BD); + if (ntfsTime.compareTo(LONG_MAX_VALUE_BD) >= 0) { + return Long.MAX_VALUE; + } + if (ntfsTime.compareTo(LONG_MIN_VALUE_BD) <= 0) { + return Long.MIN_VALUE; + } + return ntfsTime.longValue(); } /** diff --git a/src/test/java/org/apache/commons/io/file/attribute/FileTimesTest.java b/src/test/java/org/apache/commons/io/file/attribute/FileTimesTest.java index 2c074d5a3..2673c062f 100644 --- a/src/test/java/org/apache/commons/io/file/attribute/FileTimesTest.java +++ b/src/test/java/org/apache/commons/io/file/attribute/FileTimesTest.java @@ -102,11 +102,19 @@ public void testDateToFileTime(final String instant, final long ignored) { @ParameterizedTest @MethodSource("fileTimeNanoUnitsToNtfsProvider") - public void testDateToNtfsTime(final String instant, final long ntfsTime) { + public void testDateToNtfsTime(final String instantStr, final long ntfsTime) { final long ntfsMillis = Math.floorDiv(ntfsTime, FileTimes.HUNDRED_NANOS_PER_MILLISECOND) * FileTimes.HUNDRED_NANOS_PER_MILLISECOND; - final Date parsed = Date.from(Instant.parse(instant)); - assertEquals(ntfsMillis, FileTimes.toNtfsTime(parsed)); - assertEquals(ntfsMillis, FileTimes.toNtfsTime(parsed.getTime())); + final Instant instant = Instant.parse(instantStr); + final Date parsed = Date.from(instant); + final long ntfsTime2 = FileTimes.toNtfsTime(parsed); + if (ntfsTime2 == Long.MIN_VALUE || ntfsTime2 == Long.MAX_VALUE) { + // toNtfsTime returns max long instead of overflowing + } else { + assertEquals(ntfsMillis, ntfsTime2); + assertEquals(ntfsMillis, FileTimes.toNtfsTime(parsed.getTime())); + assertEquals(ntfsMillis, FileTimes.toNtfsTime(FileTimes.ntfsTimeToInstant(ntfsTime).toEpochMilli())); + } + assertEquals(ntfsTime, FileTimes.toNtfsTime(FileTimes.ntfsTimeToInstant(ntfsTime))); } @Test @@ -163,6 +171,35 @@ public void testIsUnixTimeLong(final String instant, final boolean isUnixTime) { assertEquals(isUnixTime, FileTimes.isUnixTime(Instant.parse(instant).getEpochSecond())); } + @Test + public void testMaxJavaTime() { + final long javaTime = Long.MAX_VALUE; + final Instant instant = Instant.ofEpochMilli(javaTime); + assertEquals(javaTime, instant.toEpochMilli()); // sanity check + final long ntfsTime = FileTimes.toNtfsTime(javaTime); + final Instant instant2 = FileTimes.ntfsTimeToInstant(ntfsTime); + if (ntfsTime == Long.MAX_VALUE) { + // toNtfsTime returns max long instead of overflowing + } else { + assertEquals(javaTime, instant2.toEpochMilli()); + } + } + + @ParameterizedTest + @MethodSource("fileTimeNanoUnitsToNtfsProvider") + public void testMaxJavaTimeParam(final String instantStr, final long javaTime) { + // final long javaTime = Long.MAX_VALUE; + final Instant instant = Instant.ofEpochMilli(javaTime); + assertEquals(javaTime, instant.toEpochMilli()); // sanity check + final long ntfsTime = FileTimes.toNtfsTime(javaTime); + final Instant instant2 = FileTimes.ntfsTimeToInstant(ntfsTime); + if (ntfsTime == Long.MIN_VALUE || ntfsTime == Long.MAX_VALUE) { + // toNtfsTime returns min or max long instead of overflowing + } else { + assertEquals(javaTime, instant2.toEpochMilli()); + } + } + @Test public void testMinusMillis() { final int millis = 2;