Skip to content

Commit

Permalink
Add num size limit (#827)
Browse files Browse the repository at this point in the history
  • Loading branch information
pjfanning committed Nov 29, 2022
1 parent 540ae24 commit 3af7e3e
Show file tree
Hide file tree
Showing 11 changed files with 135 additions and 42 deletions.
6 changes: 3 additions & 3 deletions src/main/java/com/fasterxml/jackson/core/JsonFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -1978,7 +1978,7 @@ protected IOContext _createContext(ContentReference contentRef, boolean resource
if (contentRef == null) {
contentRef = ContentReference.unknown();
}
return new IOContext(_getBufferRecycler(), contentRef, resourceManaged);
return new IOContext(_streamReadConstraints, _getBufferRecycler(), contentRef, resourceManaged);
}

/**
Expand All @@ -1993,7 +1993,7 @@ protected IOContext _createContext(ContentReference contentRef, boolean resource
*/
@Deprecated // @since 2.13
protected IOContext _createContext(Object rawContentRef, boolean resourceManaged) {
return new IOContext(_getBufferRecycler(),
return new IOContext(_streamReadConstraints, _getBufferRecycler(),
_createContentReference(rawContentRef),
resourceManaged);
}
Expand All @@ -2011,7 +2011,7 @@ protected IOContext _createContext(Object rawContentRef, boolean resourceManaged
protected IOContext _createNonBlockingContext(Object srcRef) {
// [jackson-core#479]: allow recycling for non-blocking parser again
// now that access is thread-safe
return new IOContext(_getBufferRecycler(),
return new IOContext(_streamReadConstraints, _getBufferRecycler(),
_createContentReference(srcRef),
false);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ public class StreamReadConstraints {

private final int _maxNumLen;

public static final StreamReadConstraints UNLIMITED = new StreamReadConstraints(Integer.MAX_VALUE);

public static final class Builder {
private int _maxNumLen = StreamReadConstraints.DEFAULT_MAX_NUM_LEN;

Expand Down
18 changes: 16 additions & 2 deletions src/main/java/com/fasterxml/jackson/core/base/ParserBase.java
Original file line number Diff line number Diff line change
Expand Up @@ -938,10 +938,16 @@ private void _parseSlowInt(int expType) throws IOException
_reportTooLongIntegral(expType, numStr);
}
if ((expType == NR_DOUBLE) || (expType == NR_FLOAT)) {
if (getMaxNumLen() >= 0 && numStr.length() > getMaxNumLen()) {
throw new NumberFormatException("number length exceeds the max number length of " + getMaxNumLen());
}
_numberDouble = NumberInput.parseDouble(numStr, isEnabled(Feature.USE_FAST_DOUBLE_PARSER));
_numTypesValid = NR_DOUBLE;
} else {
// nope, need the heavy guns... (rare case) - since Jackson v2.14, BigInteger parsing is lazy
if (getMaxNumLen() >= 0 && numStr.length() > getMaxNumLen()) {
throw new NumberFormatException("number length exceeds the max number length of " + getMaxNumLen());
}
_numberBigInt = null;
_numberString = numStr;
_numTypesValid = NR_BIGINT;
Expand Down Expand Up @@ -973,7 +979,7 @@ protected void convertNumberToInt() throws IOException
{
// First, converting from long ought to be easy
if ((_numTypesValid & NR_LONG) != 0) {
// Let's verify it's lossless conversion by simple roundtrip
// Let's verify its lossless conversion by simple roundtrip
int result = (int) _numberLong;
if (((long) result) != _numberLong) {
reportOverflowInt(getText(), currentToken());
Expand Down Expand Up @@ -1111,7 +1117,11 @@ protected void convertNumberToBigDecimal() throws IOException
if ((_numTypesValid & NR_DOUBLE) != 0) {
// Let's actually parse from String representation, to avoid
// rounding errors that non-decimal floating operations could incur
_numberBigDecimal = NumberInput.parseBigDecimal(getText());
final String numStr = getText();
if (getMaxNumLen() >= 0 && numStr.length() > getMaxNumLen()) {
throw new NumberFormatException("number length exceeds the max number length of " + getMaxNumLen());
}
_numberBigDecimal = NumberInput.parseBigDecimal(numStr);
} else if ((_numTypesValid & NR_BIGINT) != 0) {
_numberBigDecimal = new BigDecimal(_getBigInteger());
} else if ((_numTypesValid & NR_LONG) != 0) {
Expand Down Expand Up @@ -1395,4 +1405,8 @@ protected void loadMoreGuaranteed() throws IOException {
// Can't declare as deprecated, for now, but shouldn't be needed
protected void _finishString() throws IOException { }

protected int getMaxNumLen() {
return _ioContext.streamReadConstraints().getMaxNumberLength();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -384,7 +384,7 @@ public int getValueAsInt(int defaultValue) throws IOException
if (t != null) {
switch (t.id()) {
case ID_STRING:
String str = getText();
final String str = getText();
if (_hasTextualNull(str)) {
return 0;
}
Expand Down Expand Up @@ -425,7 +425,7 @@ public long getValueAsLong(long defaultValue) throws IOException
if (t != null) {
switch (t.id()) {
case ID_STRING:
String str = getText();
final String str = getText();
if (_hasTextualNull(str)) {
return 0L;
}
Expand Down Expand Up @@ -456,6 +456,9 @@ public double getValueAsDouble(double defaultValue) throws IOException
if (_hasTextualNull(str)) {
return 0L;
}
if (getMaxNumLen() >= 0 && str.length() > getMaxNumLen()) {
throw new NumberFormatException("number length exceeds the max number length of " + getMaxNumLen());
}
return NumberInput.parseAsDouble(str, defaultValue);
case ID_NUMBER_INT:
case ID_NUMBER_FLOAT:
Expand Down Expand Up @@ -788,4 +791,6 @@ protected static String _ascii(byte[] b) {
throw new RuntimeException(e);
}
}

protected abstract int getMaxNumLen();
}
37 changes: 33 additions & 4 deletions src/main/java/com/fasterxml/jackson/core/io/IOContext.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.fasterxml.jackson.core.io;

import com.fasterxml.jackson.core.JsonEncoding;
import com.fasterxml.jackson.core.StreamReadConstraints;
import com.fasterxml.jackson.core.util.BufferRecycler;
import com.fasterxml.jackson.core.util.TextBuffer;

Expand Down Expand Up @@ -60,6 +61,8 @@ public class IOContext
*/
protected final BufferRecycler _bufferRecycler;

protected final StreamReadConstraints _streamReadConstraints;

/**
* Reference to the allocated I/O buffer for low-level input reading,
* if any allocated.
Expand Down Expand Up @@ -108,26 +111,52 @@ public class IOContext

/**
* Main constructor to use.
*
*
* @param streamReadConstraints constraints for streaming reads
* @param br BufferRecycler to use, if any ({@code null} if none)
* @param contentRef Input source reference for location reporting
* @param managedResource Whether input source is managed (owned) by Jackson library
*
* @since 2.13
* @since 2.15
*/
public IOContext(BufferRecycler br, ContentReference contentRef, boolean managedResource)
public IOContext(StreamReadConstraints streamReadConstraints, BufferRecycler br,
ContentReference contentRef, boolean managedResource)
{
_streamReadConstraints = streamReadConstraints == null ?
StreamReadConstraints.builder().build() :
streamReadConstraints;
_bufferRecycler = br;
_contentReference = contentRef;
_sourceRef = contentRef.getRawContent();
_managedResource = managedResource;
}

/**
* @param br BufferRecycler to use, if any ({@code null} if none)
* @param contentRef Input source reference for location reporting
* @param managedResource Whether input source is managed (owned) by Jackson library
*
* @since 2.13
*/
@Deprecated // since 2.15
public IOContext(BufferRecycler br, ContentReference contentRef, boolean managedResource)
{
this(null, br, contentRef, managedResource);
}

@Deprecated // since 2.13
public IOContext(BufferRecycler br, Object rawContent, boolean managedResource) {
this(br, ContentReference.rawReference(rawContent), managedResource);
}

/**
* @return constraints for streaming reads
* @since 2.15
*/
public StreamReadConstraints streamReadConstraints() {
return _streamReadConstraints;
}

public void setEncoding(JsonEncoding enc) {
_encoding = enc;
}
Expand Down Expand Up @@ -172,7 +201,7 @@ public ContentReference contentReference() {
*/

public TextBuffer constructTextBuffer() {
return new TextBuffer(_bufferRecycler);
return new TextBuffer(_streamReadConstraints, _bufferRecycler);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import java.io.*;

import com.fasterxml.jackson.core.StreamReadConstraints;
import com.fasterxml.jackson.core.util.BufferRecycler;
import com.fasterxml.jackson.core.util.TextBuffer;

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

public SegmentedStringWriter(BufferRecycler br) {
super();
_buffer = new TextBuffer(br);
_buffer = new TextBuffer(StreamReadConstraints.UNLIMITED, br);
}

/*
Expand Down
47 changes: 40 additions & 7 deletions src/main/java/com/fasterxml/jackson/core/util/TextBuffer.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import java.math.BigDecimal;
import java.util.*;

import com.fasterxml.jackson.core.StreamReadConstraints;
import com.fasterxml.jackson.core.io.NumberInput;

/**
Expand Down Expand Up @@ -49,6 +50,8 @@ public final class TextBuffer

private final BufferRecycler _allocator;

private final StreamReadConstraints _streamReadConstraints;

/*
/**********************************************************
/* Shared input buffers
Expand Down Expand Up @@ -120,13 +123,16 @@ public final class TextBuffer
/**********************************************************
*/

public TextBuffer(BufferRecycler allocator) {
public TextBuffer(StreamReadConstraints streamReadConstraints, BufferRecycler allocator) {
_streamReadConstraints = streamReadConstraints == null ?
StreamReadConstraints.builder().build() :
streamReadConstraints;
_allocator = allocator;
}

// @since 2.10
protected TextBuffer(BufferRecycler allocator, char[] initialSegment) {
_allocator = allocator;
protected TextBuffer(StreamReadConstraints streamReadConstraints, BufferRecycler allocator, char[] initialSegment) {
this(streamReadConstraints, allocator);
_currentSegment = initialSegment;
_currentSize = initialSegment.length;
_inputStart = -1;
Expand All @@ -144,7 +150,7 @@ protected TextBuffer(BufferRecycler allocator, char[] initialSegment) {
* @since 2.10
*/
public static TextBuffer fromInitial(char[] initialSegment) {
return new TextBuffer(null, initialSegment);
return new TextBuffer(null, null, initialSegment);
}

/**
Expand Down Expand Up @@ -491,18 +497,35 @@ public BigDecimal contentsAsDecimal() throws NumberFormatException
{
// Already got a pre-cut array?
if (_resultArray != null) {
if (_streamReadConstraints.getMaxNumberLength() >= 0 && _resultArray.length > _streamReadConstraints.getMaxNumberLength()) {
throw new NumberFormatException(
"number length exceeds the max number length of " + _streamReadConstraints.getMaxNumberLength());
}
return NumberInput.parseBigDecimal(_resultArray);
}
// Or a shared buffer?
if ((_inputStart >= 0) && (_inputBuffer != null)) {
if (_streamReadConstraints.getMaxNumberLength() >= 0 && _inputLen > _streamReadConstraints.getMaxNumberLength()) {
throw new NumberFormatException(
"number length exceeds the max number length of " + _streamReadConstraints.getMaxNumberLength());
}
return NumberInput.parseBigDecimal(_inputBuffer, _inputStart, _inputLen);
}
// Or if not, just a single buffer (the usual case)
if ((_segmentSize == 0) && (_currentSegment != null)) {
if (_streamReadConstraints.getMaxNumberLength() >= 0 && _currentSize > _streamReadConstraints.getMaxNumberLength()) {
throw new NumberFormatException(
"number length exceeds the max number length of " + _streamReadConstraints.getMaxNumberLength());
}
return NumberInput.parseBigDecimal(_currentSegment, 0, _currentSize);
}
// If not, let's just get it aggregated...
return NumberInput.parseBigDecimal(contentsAsArray());
final char[] numArray = contentsAsArray();
if (_streamReadConstraints.getMaxNumberLength() >= 0 && numArray.length > _streamReadConstraints.getMaxNumberLength()) {
throw new NumberFormatException(
"number length exceeds the max number length of " + _streamReadConstraints.getMaxNumberLength());
}
return NumberInput.parseBigDecimal(numArray);
}

/**
Expand Down Expand Up @@ -530,7 +553,12 @@ public double contentsAsDouble() throws NumberFormatException {
* @since 2.14
*/
public double contentsAsDouble(final boolean useFastParser) throws NumberFormatException {
return NumberInput.parseDouble(contentsAsString(), useFastParser);
final String numStr = contentsAsString();
if (_streamReadConstraints.getMaxNumberLength() >= 0 && numStr.length() > _streamReadConstraints.getMaxNumberLength()) {
throw new NumberFormatException(
"number length exceeds the max number length of " + _streamReadConstraints.getMaxNumberLength());
}
return NumberInput.parseDouble(numStr, useFastParser);
}

/**
Expand Down Expand Up @@ -559,7 +587,12 @@ public float contentsAsFloat() throws NumberFormatException {
* @since 2.14
*/
public float contentsAsFloat(final boolean useFastParser) throws NumberFormatException {
return NumberInput.parseFloat(contentsAsString(), useFastParser);
final String numStr = contentsAsString();
if (_streamReadConstraints.getMaxNumberLength() >= 0 && numStr.length() > _streamReadConstraints.getMaxNumberLength()) {
throw new NumberFormatException(
"number length exceeds the max number length of " + _streamReadConstraints.getMaxNumberLength());
}
return NumberInput.parseFloat(numStr, useFastParser);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,11 @@ public double getDoubleValue() {
public BigDecimal getDecimalValue() {
return null;
}

@Override
protected int getMaxNumLen() {
return -1;
}
}

public void testParserFlagDefaults() throws Exception
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@
public class NumberOverflowTest
extends com.fasterxml.jackson.core.BaseTest
{
private final JsonFactory FACTORY = new JsonFactory();
private final JsonFactory FACTORY = JsonFactory.builder()
.streamReadConstraints(StreamReadConstraints.builder().withMaxNumberLength(1000000).build())
.build();

// NOTE: this should be long enough to trigger perf problems
private final static int BIG_NUM_LEN = 199999;
Expand Down Expand Up @@ -105,7 +107,7 @@ public void testMaliciousBigIntToDouble() throws Exception
{
for (int mode : ALL_STREAMING_MODES) {
final String doc = BIG_POS_DOC;
JsonParser p = createParser(mode, doc);
JsonParser p = createParser(FACTORY, mode, doc);
assertToken(JsonToken.START_ARRAY, p.nextToken());
assertToken(JsonToken.VALUE_NUMBER_INT, p.nextToken());
double d = p.getDoubleValue();
Expand All @@ -120,7 +122,7 @@ public void testMaliciousBigIntToFloat() throws Exception
{
for (int mode : ALL_STREAMING_MODES) {
final String doc = BIG_POS_DOC;
JsonParser p = createParser(mode, doc);
JsonParser p = createParser(FACTORY, mode, doc);
assertToken(JsonToken.START_ARRAY, p.nextToken());
assertToken(JsonToken.VALUE_NUMBER_INT, p.nextToken());
float f = p.getFloatValue();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -708,16 +708,18 @@ public void testLongNumbers2() throws Exception
input.append(1);
}
final String DOC = input.toString();
JsonFactory f = new JsonFactory();
JsonFactory f = JsonFactory.builder()
.streamReadConstraints(StreamReadConstraints.builder().withMaxNumberLength(10000).build())
.build();
_testIssue160LongNumbers(f, DOC, false);
_testIssue160LongNumbers(f, DOC, true);
}

private void _testIssue160LongNumbers(JsonFactory f, String doc, boolean useStream) throws Exception
{
JsonParser p = useStream
? jsonFactory().createParser(doc.getBytes("UTF-8"))
: jsonFactory().createParser(doc);
? f.createParser(doc.getBytes("UTF-8"))
: f.createParser(doc);
assertToken(JsonToken.VALUE_NUMBER_INT, p.nextToken());
BigInteger v = p.getBigIntegerValue();
assertNull(p.nextToken());
Expand Down
Loading

0 comments on commit 3af7e3e

Please sign in to comment.