Skip to content

Commit 7db88c1

Browse files
authored
Fix infinite loop due to integer overflow when reading large strings (#1352)
1 parent 6ce1c0d commit 7db88c1

File tree

6 files changed

+102
-5
lines changed

6 files changed

+102
-5
lines changed

release-notes/CREDITS-2.x

+6-1
Original file line numberDiff line numberDiff line change
@@ -433,4 +433,9 @@ Antonin Janec (@xtonic)
433433
* Contributed #1217: Optimize char comparison using bitwise OR
434434
(2.17.0)
435435
* Contributed #1218: Simplify Unicode surrogate pair conversion for generation
436-
(2.17.0)
436+
(2.17.0)
437+
438+
Adam J. Shook (@adamjshook)
439+
* Reported, suggested fix for #1352: Fix infinite loop due to integer overflow
440+
when reading large strings
441+
(2.17.3)

release-notes/VERSION-2.x

+4
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ a pure JSON library.
2020
(contributed by @pjfanning)
2121
#1340: Missing `JsonFactory` "provides" SPI with JPMS in `jackson-core` module
2222
(contributed by @sdyura)
23+
#1352: Fix infinite loop due to integer overflow when reading large strings
24+
(reported by Adam J.S)
25+
(fix contributed by @pjfanning)
26+
2327

2428
2.17.2 (05-Jul-2024)
2529

src/main/java/com/fasterxml/jackson/core/json/UTF8StreamJsonParser.java

+5-2
Original file line numberDiff line numberDiff line change
@@ -2544,7 +2544,9 @@ private final void _finishString2(char[] outBuf, int outPtr)
25442544
outBuf = _textBuffer.finishCurrentSegment();
25452545
outPtr = 0;
25462546
}
2547-
final int max = Math.min(_inputEnd, (ptr + (outBuf.length - outPtr)));
2547+
final int max = Math.min(
2548+
_inputEnd,
2549+
InternalJacksonUtil.addOverflowSafe(ptr, outBuf.length - outPtr));
25482550
while (ptr < max) {
25492551
c = inputBuffer[ptr++] & 0xFF;
25502552
if (codes[c] != 0) {
@@ -2776,7 +2778,8 @@ protected JsonToken _handleApos() throws IOException
27762778
}
27772779
int max = _inputEnd;
27782780
{
2779-
int max2 = _inputPtr + (outBuf.length - outPtr);
2781+
final int max2 =
2782+
InternalJacksonUtil.addOverflowSafe(_inputPtr, outBuf.length - outPtr);
27802783
if (max2 < max) {
27812784
max = max2;
27822785
}

src/main/java/com/fasterxml/jackson/core/json/async/NonBlockingUtf8JsonParserBase.java

+7-2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import com.fasterxml.jackson.core.io.IOContext;
77
import com.fasterxml.jackson.core.json.JsonReadFeature;
88
import com.fasterxml.jackson.core.sym.ByteQuadsCanonicalizer;
9+
import com.fasterxml.jackson.core.util.InternalJacksonUtil;
910
import com.fasterxml.jackson.core.util.VersionUtil;
1011

1112
import java.io.IOException;
@@ -2554,7 +2555,9 @@ private final JsonToken _finishRegularString() throws IOException
25542555
outBuf = _textBuffer.finishCurrentSegment();
25552556
outPtr = 0;
25562557
}
2557-
final int max = Math.min(_inputEnd, (ptr + (outBuf.length - outPtr)));
2558+
final int max = Math.min(
2559+
_inputEnd,
2560+
InternalJacksonUtil.addOverflowSafe(ptr, outBuf.length - outPtr));
25582561
while (ptr < max) {
25592562
c = getByteFromBuffer(ptr++) & 0xFF;
25602563
if (codes[c] != 0) {
@@ -2677,7 +2680,9 @@ private final JsonToken _finishAposString() throws IOException
26772680
outBuf = _textBuffer.finishCurrentSegment();
26782681
outPtr = 0;
26792682
}
2680-
final int max = Math.min(_inputEnd, (ptr + (outBuf.length - outPtr)));
2683+
final int max = Math.min(
2684+
_inputEnd,
2685+
InternalJacksonUtil.addOverflowSafe(ptr, outBuf.length - outPtr));
26812686
while (ptr < max) {
26822687
c = getByteFromBuffer(ptr++) & 0xFF;
26832688
if ((codes[c] != 0) && (c != INT_QUOTE)) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package com.fasterxml.jackson.core.util;
2+
3+
/**
4+
* Internal Use Only. Helper class used to contain some useful utility methods.
5+
*
6+
* @since 2.17.3 / 2.18.1
7+
*/
8+
public abstract class InternalJacksonUtil {
9+
/**
10+
* Internal Use Only.
11+
* <p>
12+
* Method that will add two non-negative integers, and if result overflows, return
13+
* {@link Integer#MAX_VALUE}. For performance reasons, does NOT check for
14+
* the result being less than {@link Integer#MIN_VALUE}, nor whether arguments
15+
* are actually non-negative.
16+
* This is usually used to implement overflow-safe bounds checking.
17+
*/
18+
public static int addOverflowSafe(final int base, final int length) {
19+
int result = base + length;
20+
if (result < 0) {
21+
return Integer.MAX_VALUE;
22+
}
23+
return result;
24+
}
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package com.fasterxml.jackson.core.read;
2+
3+
import org.junit.jupiter.api.Disabled;
4+
import org.junit.jupiter.api.Test;
5+
import org.junit.jupiter.api.Timeout;
6+
7+
import com.fasterxml.jackson.core.JUnit5TestBase;
8+
import com.fasterxml.jackson.core.JsonFactory;
9+
import com.fasterxml.jackson.core.JsonParser;
10+
import com.fasterxml.jackson.core.JsonToken;
11+
import com.fasterxml.jackson.core.StreamReadConstraints;
12+
13+
import static org.junit.jupiter.api.Assertions.assertEquals;
14+
import static org.junit.jupiter.api.Assertions.assertNull;
15+
16+
import java.util.Arrays;
17+
import java.util.concurrent.TimeUnit;
18+
19+
// https://github.com/FasterXML/jackson-core/pull/1352
20+
class TestReadHumongousString extends JUnit5TestBase
21+
{
22+
// disabled because it takes too much memory to run
23+
@Disabled
24+
// Since we might get infinite loop:
25+
@Timeout(value = 10, unit = TimeUnit.SECONDS, threadMode = Timeout.ThreadMode.SEPARATE_THREAD)
26+
@Test
27+
void testLargeStringDeserialization() throws Exception {
28+
final int len = Integer.MAX_VALUE - 1024;
29+
final byte[] largeByteString = makeLongByteString(len);
30+
final JsonFactory f = JsonFactory.builder()
31+
.streamReadConstraints(StreamReadConstraints.builder()
32+
.maxStringLength(Integer.MAX_VALUE)
33+
.build())
34+
.build();
35+
36+
try (JsonParser parser = f.createParser(largeByteString)) {
37+
assertToken(JsonToken.VALUE_STRING, parser.nextToken());
38+
// Let's not construct String but just check that length is
39+
// expected: this avoids having to allocate 4 gig more of heap
40+
// for test -- should still trigger problem if fix not valid
41+
assertEquals(len, parser.getTextLength());
42+
// TODO: could use streaming accessor (`JsonParser.getText(Writer)`)
43+
assertNull(parser.nextToken());
44+
}
45+
46+
}
47+
48+
private byte[] makeLongByteString(int length) {
49+
final byte[] result = new byte[length + 2];
50+
Arrays.fill(result, (byte) 'a');
51+
result[0] = '\"';
52+
result[length + 1] = '\"';
53+
return result;
54+
}
55+
}

0 commit comments

Comments
 (0)