Skip to content

Commit 2dcd1ed

Browse files
committed
FileTimes.toNtfsTime(*) methods can overflow result values
1 parent 41b5e03 commit 2dcd1ed

File tree

3 files changed

+58
-8
lines changed

3 files changed

+58
-8
lines changed

src/changes/changes.xml

+1
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ The <action> type attribute can be add,update,fix,remove.
6969
<action dev="ggregory" type="fix" issue="IO-871" due-to="Éamonn McManus, Gary Gregory">IOUtils.contentEquals is incorrect when InputStream.available under-reports.</action>
7070
<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>
7171
<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>
72+
<action dev="ggregory" type="fix" due-to="Gary Gregory">FileTimes.toNtfsTime(*) methods can overflow result values.</action>
7273
<!-- ADD -->
7374
<action dev="ggregory" type="add" issue="IO-860" due-to="Nico Strecker, Gary Gregory">Add ThrottledInputStream.Builder.setMaxBytes(long, ChronoUnit).</action>
7475
<action dev="ggregory" type="add" due-to="Gary Gregory">Add IOIterable.</action>

src/main/java/org/apache/commons/io/file/attribute/FileTimes.java

+16-4
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@
4040
*/
4141
public final class FileTimes {
4242

43+
private static final BigDecimal LONG_MIN_VALUE_BD = BigDecimal.valueOf(Long.MIN_VALUE);
44+
45+
private static final BigDecimal LONG_MAX_VALUE_BD = BigDecimal.valueOf(Long.MAX_VALUE);
46+
4347
private static final MathContext MATH_CONTEXT = new MathContext(0, RoundingMode.FLOOR);
4448

4549
/**
@@ -74,6 +78,8 @@ public final class FileTimes {
7478
*/
7579
static final long HUNDRED_NANOS_PER_MILLISECOND = TimeUnit.MILLISECONDS.toNanos(1) / 100;
7680

81+
static final BigDecimal HUNDRED_NANOS_PER_MILLISECOND_BD = BigDecimal.valueOf(HUNDRED_NANOS_PER_MILLISECOND);
82+
7783
private static final long HUNDRED = 100L;
7884

7985
private static final BigDecimal HUNDRED_BD = BigDecimal.valueOf(HUNDRED);
@@ -274,8 +280,7 @@ public static FileTime toFileTime(final Date date) {
274280
* @return the NTFS time, 100-nanosecond units since 1 January 1601.
275281
*/
276282
public static long toNtfsTime(final Date date) {
277-
final long javaHundredNanos = date.getTime() * HUNDRED_NANOS_PER_MILLISECOND;
278-
return Math.subtractExact(javaHundredNanos, UNIX_TO_NTFS_OFFSET);
283+
return toNtfsTime(date.getTime());
279284
}
280285

281286
/**
@@ -308,8 +313,15 @@ static long toNtfsTime(final Instant instant) {
308313
* @since 2.16.0
309314
*/
310315
public static long toNtfsTime(final long javaTime) {
311-
final long javaHundredNanos = javaTime * HUNDRED_NANOS_PER_MILLISECOND;
312-
return Math.subtractExact(javaHundredNanos, UNIX_TO_NTFS_OFFSET);
316+
final BigDecimal javaHundredNanos = BigDecimal.valueOf(javaTime).multiply(HUNDRED_NANOS_PER_MILLISECOND_BD);
317+
final BigDecimal ntfsTime = javaHundredNanos.subtract(UNIX_TO_NTFS_OFFSET_BD);
318+
if (ntfsTime.compareTo(LONG_MAX_VALUE_BD) >= 0) {
319+
return Long.MAX_VALUE;
320+
}
321+
if (ntfsTime.compareTo(LONG_MIN_VALUE_BD) <= 0) {
322+
return Long.MIN_VALUE;
323+
}
324+
return ntfsTime.longValue();
313325
}
314326

315327
/**

src/test/java/org/apache/commons/io/file/attribute/FileTimesTest.java

+41-4
Original file line numberDiff line numberDiff line change
@@ -102,11 +102,19 @@ public void testDateToFileTime(final String instant, final long ignored) {
102102

103103
@ParameterizedTest
104104
@MethodSource("fileTimeNanoUnitsToNtfsProvider")
105-
public void testDateToNtfsTime(final String instant, final long ntfsTime) {
105+
public void testDateToNtfsTime(final String instantStr, final long ntfsTime) {
106106
final long ntfsMillis = Math.floorDiv(ntfsTime, FileTimes.HUNDRED_NANOS_PER_MILLISECOND) * FileTimes.HUNDRED_NANOS_PER_MILLISECOND;
107-
final Date parsed = Date.from(Instant.parse(instant));
108-
assertEquals(ntfsMillis, FileTimes.toNtfsTime(parsed));
109-
assertEquals(ntfsMillis, FileTimes.toNtfsTime(parsed.getTime()));
107+
final Instant instant = Instant.parse(instantStr);
108+
final Date parsed = Date.from(instant);
109+
final long ntfsTime2 = FileTimes.toNtfsTime(parsed);
110+
if (ntfsTime2 == Long.MIN_VALUE || ntfsTime2 == Long.MAX_VALUE) {
111+
// toNtfsTime returns max long instead of overflowing
112+
} else {
113+
assertEquals(ntfsMillis, ntfsTime2);
114+
assertEquals(ntfsMillis, FileTimes.toNtfsTime(parsed.getTime()));
115+
assertEquals(ntfsMillis, FileTimes.toNtfsTime(FileTimes.ntfsTimeToInstant(ntfsTime).toEpochMilli()));
116+
}
117+
assertEquals(ntfsTime, FileTimes.toNtfsTime(FileTimes.ntfsTimeToInstant(ntfsTime)));
110118
}
111119

112120
@Test
@@ -163,6 +171,35 @@ public void testIsUnixTimeLong(final String instant, final boolean isUnixTime) {
163171
assertEquals(isUnixTime, FileTimes.isUnixTime(Instant.parse(instant).getEpochSecond()));
164172
}
165173

174+
@Test
175+
public void testMaxJavaTime() {
176+
final long javaTime = Long.MAX_VALUE;
177+
final Instant instant = Instant.ofEpochMilli(javaTime);
178+
assertEquals(javaTime, instant.toEpochMilli()); // sanity check
179+
final long ntfsTime = FileTimes.toNtfsTime(javaTime);
180+
final Instant instant2 = FileTimes.ntfsTimeToInstant(ntfsTime);
181+
if (ntfsTime == Long.MAX_VALUE) {
182+
// toNtfsTime returns max long instead of overflowing
183+
} else {
184+
assertEquals(javaTime, instant2.toEpochMilli());
185+
}
186+
}
187+
188+
@ParameterizedTest
189+
@MethodSource("fileTimeNanoUnitsToNtfsProvider")
190+
public void testMaxJavaTimeParam(final String instantStr, final long javaTime) {
191+
// final long javaTime = Long.MAX_VALUE;
192+
final Instant instant = Instant.ofEpochMilli(javaTime);
193+
assertEquals(javaTime, instant.toEpochMilli()); // sanity check
194+
final long ntfsTime = FileTimes.toNtfsTime(javaTime);
195+
final Instant instant2 = FileTimes.ntfsTimeToInstant(ntfsTime);
196+
if (ntfsTime == Long.MIN_VALUE || ntfsTime == Long.MAX_VALUE) {
197+
// toNtfsTime returns min or max long instead of overflowing
198+
} else {
199+
assertEquals(javaTime, instant2.toEpochMilli());
200+
}
201+
}
202+
166203
@Test
167204
public void testMinusMillis() {
168205
final int millis = 2;

0 commit comments

Comments
 (0)