Skip to content

Commit 3af7e3e

Browse files
authored
Add num size limit (#827)
1 parent 540ae24 commit 3af7e3e

File tree

11 files changed

+135
-42
lines changed

11 files changed

+135
-42
lines changed

src/main/java/com/fasterxml/jackson/core/JsonFactory.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1978,7 +1978,7 @@ protected IOContext _createContext(ContentReference contentRef, boolean resource
19781978
if (contentRef == null) {
19791979
contentRef = ContentReference.unknown();
19801980
}
1981-
return new IOContext(_getBufferRecycler(), contentRef, resourceManaged);
1981+
return new IOContext(_streamReadConstraints, _getBufferRecycler(), contentRef, resourceManaged);
19821982
}
19831983

19841984
/**
@@ -1993,7 +1993,7 @@ protected IOContext _createContext(ContentReference contentRef, boolean resource
19931993
*/
19941994
@Deprecated // @since 2.13
19951995
protected IOContext _createContext(Object rawContentRef, boolean resourceManaged) {
1996-
return new IOContext(_getBufferRecycler(),
1996+
return new IOContext(_streamReadConstraints, _getBufferRecycler(),
19971997
_createContentReference(rawContentRef),
19981998
resourceManaged);
19991999
}
@@ -2011,7 +2011,7 @@ protected IOContext _createContext(Object rawContentRef, boolean resourceManaged
20112011
protected IOContext _createNonBlockingContext(Object srcRef) {
20122012
// [jackson-core#479]: allow recycling for non-blocking parser again
20132013
// now that access is thread-safe
2014-
return new IOContext(_getBufferRecycler(),
2014+
return new IOContext(_streamReadConstraints, _getBufferRecycler(),
20152015
_createContentReference(srcRef),
20162016
false);
20172017
}

src/main/java/com/fasterxml/jackson/core/StreamReadConstraints.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ public class StreamReadConstraints {
1111

1212
private final int _maxNumLen;
1313

14+
public static final StreamReadConstraints UNLIMITED = new StreamReadConstraints(Integer.MAX_VALUE);
15+
1416
public static final class Builder {
1517
private int _maxNumLen = StreamReadConstraints.DEFAULT_MAX_NUM_LEN;
1618

src/main/java/com/fasterxml/jackson/core/base/ParserBase.java

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -938,10 +938,16 @@ private void _parseSlowInt(int expType) throws IOException
938938
_reportTooLongIntegral(expType, numStr);
939939
}
940940
if ((expType == NR_DOUBLE) || (expType == NR_FLOAT)) {
941+
if (getMaxNumLen() >= 0 && numStr.length() > getMaxNumLen()) {
942+
throw new NumberFormatException("number length exceeds the max number length of " + getMaxNumLen());
943+
}
941944
_numberDouble = NumberInput.parseDouble(numStr, isEnabled(Feature.USE_FAST_DOUBLE_PARSER));
942945
_numTypesValid = NR_DOUBLE;
943946
} else {
944947
// nope, need the heavy guns... (rare case) - since Jackson v2.14, BigInteger parsing is lazy
948+
if (getMaxNumLen() >= 0 && numStr.length() > getMaxNumLen()) {
949+
throw new NumberFormatException("number length exceeds the max number length of " + getMaxNumLen());
950+
}
945951
_numberBigInt = null;
946952
_numberString = numStr;
947953
_numTypesValid = NR_BIGINT;
@@ -973,7 +979,7 @@ protected void convertNumberToInt() throws IOException
973979
{
974980
// First, converting from long ought to be easy
975981
if ((_numTypesValid & NR_LONG) != 0) {
976-
// Let's verify it's lossless conversion by simple roundtrip
982+
// Let's verify its lossless conversion by simple roundtrip
977983
int result = (int) _numberLong;
978984
if (((long) result) != _numberLong) {
979985
reportOverflowInt(getText(), currentToken());
@@ -1111,7 +1117,11 @@ protected void convertNumberToBigDecimal() throws IOException
11111117
if ((_numTypesValid & NR_DOUBLE) != 0) {
11121118
// Let's actually parse from String representation, to avoid
11131119
// rounding errors that non-decimal floating operations could incur
1114-
_numberBigDecimal = NumberInput.parseBigDecimal(getText());
1120+
final String numStr = getText();
1121+
if (getMaxNumLen() >= 0 && numStr.length() > getMaxNumLen()) {
1122+
throw new NumberFormatException("number length exceeds the max number length of " + getMaxNumLen());
1123+
}
1124+
_numberBigDecimal = NumberInput.parseBigDecimal(numStr);
11151125
} else if ((_numTypesValid & NR_BIGINT) != 0) {
11161126
_numberBigDecimal = new BigDecimal(_getBigInteger());
11171127
} else if ((_numTypesValid & NR_LONG) != 0) {
@@ -1395,4 +1405,8 @@ protected void loadMoreGuaranteed() throws IOException {
13951405
// Can't declare as deprecated, for now, but shouldn't be needed
13961406
protected void _finishString() throws IOException { }
13971407

1408+
protected int getMaxNumLen() {
1409+
return _ioContext.streamReadConstraints().getMaxNumberLength();
1410+
}
1411+
13981412
}

src/main/java/com/fasterxml/jackson/core/base/ParserMinimalBase.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -384,7 +384,7 @@ public int getValueAsInt(int defaultValue) throws IOException
384384
if (t != null) {
385385
switch (t.id()) {
386386
case ID_STRING:
387-
String str = getText();
387+
final String str = getText();
388388
if (_hasTextualNull(str)) {
389389
return 0;
390390
}
@@ -425,7 +425,7 @@ public long getValueAsLong(long defaultValue) throws IOException
425425
if (t != null) {
426426
switch (t.id()) {
427427
case ID_STRING:
428-
String str = getText();
428+
final String str = getText();
429429
if (_hasTextualNull(str)) {
430430
return 0L;
431431
}
@@ -456,6 +456,9 @@ public double getValueAsDouble(double defaultValue) throws IOException
456456
if (_hasTextualNull(str)) {
457457
return 0L;
458458
}
459+
if (getMaxNumLen() >= 0 && str.length() > getMaxNumLen()) {
460+
throw new NumberFormatException("number length exceeds the max number length of " + getMaxNumLen());
461+
}
459462
return NumberInput.parseAsDouble(str, defaultValue);
460463
case ID_NUMBER_INT:
461464
case ID_NUMBER_FLOAT:
@@ -788,4 +791,6 @@ protected static String _ascii(byte[] b) {
788791
throw new RuntimeException(e);
789792
}
790793
}
794+
795+
protected abstract int getMaxNumLen();
791796
}

src/main/java/com/fasterxml/jackson/core/io/IOContext.java

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.fasterxml.jackson.core.io;
22

33
import com.fasterxml.jackson.core.JsonEncoding;
4+
import com.fasterxml.jackson.core.StreamReadConstraints;
45
import com.fasterxml.jackson.core.util.BufferRecycler;
56
import com.fasterxml.jackson.core.util.TextBuffer;
67

@@ -60,6 +61,8 @@ public class IOContext
6061
*/
6162
protected final BufferRecycler _bufferRecycler;
6263

64+
protected final StreamReadConstraints _streamReadConstraints;
65+
6366
/**
6467
* Reference to the allocated I/O buffer for low-level input reading,
6568
* if any allocated.
@@ -108,26 +111,52 @@ public class IOContext
108111

109112
/**
110113
* Main constructor to use.
111-
*
114+
*
115+
* @param streamReadConstraints constraints for streaming reads
112116
* @param br BufferRecycler to use, if any ({@code null} if none)
113117
* @param contentRef Input source reference for location reporting
114118
* @param managedResource Whether input source is managed (owned) by Jackson library
115119
*
116-
* @since 2.13
120+
* @since 2.15
117121
*/
118-
public IOContext(BufferRecycler br, ContentReference contentRef, boolean managedResource)
122+
public IOContext(StreamReadConstraints streamReadConstraints, BufferRecycler br,
123+
ContentReference contentRef, boolean managedResource)
119124
{
125+
_streamReadConstraints = streamReadConstraints == null ?
126+
StreamReadConstraints.builder().build() :
127+
streamReadConstraints;
120128
_bufferRecycler = br;
121129
_contentReference = contentRef;
122130
_sourceRef = contentRef.getRawContent();
123131
_managedResource = managedResource;
124132
}
125133

134+
/**
135+
* @param br BufferRecycler to use, if any ({@code null} if none)
136+
* @param contentRef Input source reference for location reporting
137+
* @param managedResource Whether input source is managed (owned) by Jackson library
138+
*
139+
* @since 2.13
140+
*/
141+
@Deprecated // since 2.15
142+
public IOContext(BufferRecycler br, ContentReference contentRef, boolean managedResource)
143+
{
144+
this(null, br, contentRef, managedResource);
145+
}
146+
126147
@Deprecated // since 2.13
127148
public IOContext(BufferRecycler br, Object rawContent, boolean managedResource) {
128149
this(br, ContentReference.rawReference(rawContent), managedResource);
129150
}
130151

152+
/**
153+
* @return constraints for streaming reads
154+
* @since 2.15
155+
*/
156+
public StreamReadConstraints streamReadConstraints() {
157+
return _streamReadConstraints;
158+
}
159+
131160
public void setEncoding(JsonEncoding enc) {
132161
_encoding = enc;
133162
}
@@ -172,7 +201,7 @@ public ContentReference contentReference() {
172201
*/
173202

174203
public TextBuffer constructTextBuffer() {
175-
return new TextBuffer(_bufferRecycler);
204+
return new TextBuffer(_streamReadConstraints, _bufferRecycler);
176205
}
177206

178207
/**

src/main/java/com/fasterxml/jackson/core/io/SegmentedStringWriter.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import java.io.*;
44

5+
import com.fasterxml.jackson.core.StreamReadConstraints;
56
import com.fasterxml.jackson.core.util.BufferRecycler;
67
import com.fasterxml.jackson.core.util.TextBuffer;
78

@@ -19,7 +20,7 @@ public final class SegmentedStringWriter extends Writer
1920

2021
public SegmentedStringWriter(BufferRecycler br) {
2122
super();
22-
_buffer = new TextBuffer(br);
23+
_buffer = new TextBuffer(StreamReadConstraints.UNLIMITED, br);
2324
}
2425

2526
/*

src/main/java/com/fasterxml/jackson/core/util/TextBuffer.java

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import java.math.BigDecimal;
55
import java.util.*;
66

7+
import com.fasterxml.jackson.core.StreamReadConstraints;
78
import com.fasterxml.jackson.core.io.NumberInput;
89

910
/**
@@ -49,6 +50,8 @@ public final class TextBuffer
4950

5051
private final BufferRecycler _allocator;
5152

53+
private final StreamReadConstraints _streamReadConstraints;
54+
5255
/*
5356
/**********************************************************
5457
/* Shared input buffers
@@ -120,13 +123,16 @@ public final class TextBuffer
120123
/**********************************************************
121124
*/
122125

123-
public TextBuffer(BufferRecycler allocator) {
126+
public TextBuffer(StreamReadConstraints streamReadConstraints, BufferRecycler allocator) {
127+
_streamReadConstraints = streamReadConstraints == null ?
128+
StreamReadConstraints.builder().build() :
129+
streamReadConstraints;
124130
_allocator = allocator;
125131
}
126132

127133
// @since 2.10
128-
protected TextBuffer(BufferRecycler allocator, char[] initialSegment) {
129-
_allocator = allocator;
134+
protected TextBuffer(StreamReadConstraints streamReadConstraints, BufferRecycler allocator, char[] initialSegment) {
135+
this(streamReadConstraints, allocator);
130136
_currentSegment = initialSegment;
131137
_currentSize = initialSegment.length;
132138
_inputStart = -1;
@@ -144,7 +150,7 @@ protected TextBuffer(BufferRecycler allocator, char[] initialSegment) {
144150
* @since 2.10
145151
*/
146152
public static TextBuffer fromInitial(char[] initialSegment) {
147-
return new TextBuffer(null, initialSegment);
153+
return new TextBuffer(null, null, initialSegment);
148154
}
149155

150156
/**
@@ -491,18 +497,35 @@ public BigDecimal contentsAsDecimal() throws NumberFormatException
491497
{
492498
// Already got a pre-cut array?
493499
if (_resultArray != null) {
500+
if (_streamReadConstraints.getMaxNumberLength() >= 0 && _resultArray.length > _streamReadConstraints.getMaxNumberLength()) {
501+
throw new NumberFormatException(
502+
"number length exceeds the max number length of " + _streamReadConstraints.getMaxNumberLength());
503+
}
494504
return NumberInput.parseBigDecimal(_resultArray);
495505
}
496506
// Or a shared buffer?
497507
if ((_inputStart >= 0) && (_inputBuffer != null)) {
508+
if (_streamReadConstraints.getMaxNumberLength() >= 0 && _inputLen > _streamReadConstraints.getMaxNumberLength()) {
509+
throw new NumberFormatException(
510+
"number length exceeds the max number length of " + _streamReadConstraints.getMaxNumberLength());
511+
}
498512
return NumberInput.parseBigDecimal(_inputBuffer, _inputStart, _inputLen);
499513
}
500514
// Or if not, just a single buffer (the usual case)
501515
if ((_segmentSize == 0) && (_currentSegment != null)) {
516+
if (_streamReadConstraints.getMaxNumberLength() >= 0 && _currentSize > _streamReadConstraints.getMaxNumberLength()) {
517+
throw new NumberFormatException(
518+
"number length exceeds the max number length of " + _streamReadConstraints.getMaxNumberLength());
519+
}
502520
return NumberInput.parseBigDecimal(_currentSegment, 0, _currentSize);
503521
}
504522
// If not, let's just get it aggregated...
505-
return NumberInput.parseBigDecimal(contentsAsArray());
523+
final char[] numArray = contentsAsArray();
524+
if (_streamReadConstraints.getMaxNumberLength() >= 0 && numArray.length > _streamReadConstraints.getMaxNumberLength()) {
525+
throw new NumberFormatException(
526+
"number length exceeds the max number length of " + _streamReadConstraints.getMaxNumberLength());
527+
}
528+
return NumberInput.parseBigDecimal(numArray);
506529
}
507530

508531
/**
@@ -530,7 +553,12 @@ public double contentsAsDouble() throws NumberFormatException {
530553
* @since 2.14
531554
*/
532555
public double contentsAsDouble(final boolean useFastParser) throws NumberFormatException {
533-
return NumberInput.parseDouble(contentsAsString(), useFastParser);
556+
final String numStr = contentsAsString();
557+
if (_streamReadConstraints.getMaxNumberLength() >= 0 && numStr.length() > _streamReadConstraints.getMaxNumberLength()) {
558+
throw new NumberFormatException(
559+
"number length exceeds the max number length of " + _streamReadConstraints.getMaxNumberLength());
560+
}
561+
return NumberInput.parseDouble(numStr, useFastParser);
534562
}
535563

536564
/**
@@ -559,7 +587,12 @@ public float contentsAsFloat() throws NumberFormatException {
559587
* @since 2.14
560588
*/
561589
public float contentsAsFloat(final boolean useFastParser) throws NumberFormatException {
562-
return NumberInput.parseFloat(contentsAsString(), useFastParser);
590+
final String numStr = contentsAsString();
591+
if (_streamReadConstraints.getMaxNumberLength() >= 0 && numStr.length() > _streamReadConstraints.getMaxNumberLength()) {
592+
throw new NumberFormatException(
593+
"number length exceeds the max number length of " + _streamReadConstraints.getMaxNumberLength());
594+
}
595+
return NumberInput.parseFloat(numStr, useFastParser);
563596
}
564597

565598
/**

src/test/java/com/fasterxml/jackson/core/ParserFeatureDefaultsTest.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,11 @@ public double getDoubleValue() {
134134
public BigDecimal getDecimalValue() {
135135
return null;
136136
}
137+
138+
@Override
139+
protected int getMaxNumLen() {
140+
return -1;
141+
}
137142
}
138143

139144
public void testParserFlagDefaults() throws Exception

src/test/java/com/fasterxml/jackson/core/read/NumberOverflowTest.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@
88
public class NumberOverflowTest
99
extends com.fasterxml.jackson.core.BaseTest
1010
{
11-
private final JsonFactory FACTORY = new JsonFactory();
11+
private final JsonFactory FACTORY = JsonFactory.builder()
12+
.streamReadConstraints(StreamReadConstraints.builder().withMaxNumberLength(1000000).build())
13+
.build();
1214

1315
// NOTE: this should be long enough to trigger perf problems
1416
private final static int BIG_NUM_LEN = 199999;
@@ -105,7 +107,7 @@ public void testMaliciousBigIntToDouble() throws Exception
105107
{
106108
for (int mode : ALL_STREAMING_MODES) {
107109
final String doc = BIG_POS_DOC;
108-
JsonParser p = createParser(mode, doc);
110+
JsonParser p = createParser(FACTORY, mode, doc);
109111
assertToken(JsonToken.START_ARRAY, p.nextToken());
110112
assertToken(JsonToken.VALUE_NUMBER_INT, p.nextToken());
111113
double d = p.getDoubleValue();
@@ -120,7 +122,7 @@ public void testMaliciousBigIntToFloat() throws Exception
120122
{
121123
for (int mode : ALL_STREAMING_MODES) {
122124
final String doc = BIG_POS_DOC;
123-
JsonParser p = createParser(mode, doc);
125+
JsonParser p = createParser(FACTORY, mode, doc);
124126
assertToken(JsonToken.START_ARRAY, p.nextToken());
125127
assertToken(JsonToken.VALUE_NUMBER_INT, p.nextToken());
126128
float f = p.getFloatValue();

src/test/java/com/fasterxml/jackson/core/read/NumberParsingTest.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -708,16 +708,18 @@ public void testLongNumbers2() throws Exception
708708
input.append(1);
709709
}
710710
final String DOC = input.toString();
711-
JsonFactory f = new JsonFactory();
711+
JsonFactory f = JsonFactory.builder()
712+
.streamReadConstraints(StreamReadConstraints.builder().withMaxNumberLength(10000).build())
713+
.build();
712714
_testIssue160LongNumbers(f, DOC, false);
713715
_testIssue160LongNumbers(f, DOC, true);
714716
}
715717

716718
private void _testIssue160LongNumbers(JsonFactory f, String doc, boolean useStream) throws Exception
717719
{
718720
JsonParser p = useStream
719-
? jsonFactory().createParser(doc.getBytes("UTF-8"))
720-
: jsonFactory().createParser(doc);
721+
? f.createParser(doc.getBytes("UTF-8"))
722+
: f.createParser(doc);
721723
assertToken(JsonToken.VALUE_NUMBER_INT, p.nextToken());
722724
BigInteger v = p.getBigIntegerValue();
723725
assertNull(p.nextToken());

0 commit comments

Comments
 (0)