Skip to content

Commit

Permalink
Add "max-doc-len" limit, enforcement (#1101)
Browse files Browse the repository at this point in the history
  • Loading branch information
cowtowncoder authored Sep 8, 2023
1 parent 228d3f5 commit f511bcd
Show file tree
Hide file tree
Showing 11 changed files with 249 additions and 29 deletions.
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,12 +117,14 @@ Implemented limits are:

### Input parsing limits

* Maximum number token length (2.15+): (see https://github.com/FasterXML/jackson-core/issues/815)
* Maximum Number token length (2.15+): (see https://github.com/FasterXML/jackson-core/issues/815)
* Default: Maximum 1000 for both integral and floating-point numbers.
* Maximum String value length (2.15+): (see https://github.com/FasterXML/jackson-core/issues/863)
* Default: 20_000_000 (20 million) (since 2.15.1; 2.15.0 had lower limit, 5 million)
* Maximum Input nesting depth (2.15+): (see https://github.com/FasterXML/jackson-core/pull/943)
* Default: 1000 levels
* Maximum Document length (2.16+): (see https://github.com/FasterXML/jackson-core/issues/1046)
* Default: Unlimited (-1)

### Output generation limits

Expand All @@ -137,6 +139,7 @@ You can change per-factory limits as follows:

```java
JsonFactory f = JsonFactory.builder()
.streamReadConstraints(StreamReadConstraints.builder().maxDocumentLength(10_000_000L).build())
.streamReadConstraints(StreamReadConstraints.builder().maxNumberLength(250).build())
.streamWriteConstraints(StreamWriteConstraints.builder().maxNestingDepth(2000).build())
.build();
Expand Down
2 changes: 2 additions & 0 deletions release-notes/VERSION-2.x
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ a pure JSON library.
#1041: Start using AssertJ in unit tests
#1042: Allow configuring spaces before and/or after the colon in `DefaultPrettyPrinter`
(contributed by @digulla)
#1046: Add configurable limit for the maximum number of bytes/chars
of content to parse before failing
#1047: Add configurable limit for the maximum length of Object property names
to parse before failing (default max: 50,000 chars)
(contributed by @pjfanning)
Expand Down
117 changes: 106 additions & 11 deletions src/main/java/com/fasterxml/jackson/core/StreamReadConstraints.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@
* </li>
* <li>Maximum Nesting depth: default 1000 (see {@link #DEFAULT_MAX_DEPTH})
* </li>
* <li>Maximum Document length: default {@code unlimited} (coded as {@code -1},
* (see {@link #DEFAULT_MAX_DOC_LEN})
* </li>
* </ul>
*
* @since 2.15
Expand All @@ -35,7 +38,13 @@ public class StreamReadConstraints
public static final int DEFAULT_MAX_DEPTH = 1000;

/**
* Default setting for maximum number length: see {@link Builder#maxNumberLength(int)} for details.
* Default setting for maximum document length:
* see {@link Builder#maxDocumentLength} for details.
*/
public static final long DEFAULT_MAX_DOC_LEN = -1L;

/**
* @since 2.16
*/
public static final int DEFAULT_MAX_NUM_LEN = 1000;

Expand Down Expand Up @@ -64,13 +73,16 @@ public class StreamReadConstraints
private static final int MAX_BIGINT_SCALE_MAGNITUDE = 100_000;

protected final int _maxNestingDepth;
protected final long _maxDocLen;

protected final int _maxNumLen;
protected final int _maxStringLen;
protected final int _maxNameLen;

private static StreamReadConstraints DEFAULT =
new StreamReadConstraints(DEFAULT_MAX_DEPTH, DEFAULT_MAX_NUM_LEN,
DEFAULT_MAX_STRING_LEN, DEFAULT_MAX_NAME_LEN);
new StreamReadConstraints(DEFAULT_MAX_DEPTH,
DEFAULT_MAX_DOC_LEN,
DEFAULT_MAX_NUM_LEN, DEFAULT_MAX_STRING_LEN, DEFAULT_MAX_NAME_LEN);

/**
* Override the default StreamReadConstraints. These defaults are only used when {@link JsonFactory}
Expand All @@ -91,13 +103,15 @@ public class StreamReadConstraints
*/
public static void overrideDefaultStreamReadConstraints(final StreamReadConstraints streamReadConstraints) {
if (streamReadConstraints == null) {
DEFAULT = new StreamReadConstraints(DEFAULT_MAX_DEPTH, DEFAULT_MAX_NUM_LEN, DEFAULT_MAX_STRING_LEN);
DEFAULT = new StreamReadConstraints(DEFAULT_MAX_DEPTH, DEFAULT_MAX_DOC_LEN,
DEFAULT_MAX_NUM_LEN, DEFAULT_MAX_STRING_LEN);
} else {
DEFAULT = streamReadConstraints;
}
}

public static final class Builder {
private long maxDocLen;
private int maxNestingDepth;
private int maxNumLen;
private int maxStringLen;
Expand All @@ -120,6 +134,28 @@ public Builder maxNestingDepth(final int maxNestingDepth) {
return this;
}

/**
* Sets the maximum allowed document length (for positive values over 0) or
* indicate that any length is acceptable ({@code 0} or negative number).
* The length is in input units of the input source, that is, in
* {@code byte}s or {@code char}s.
*
* @param maxDocLen the maximum allowed document if positive number above 0; otherwise
* ({@code 0} or negative number) means "unlimited".
*
* @return this builder
*
* @since 2.16
*/
public Builder maxDocumentLength(long maxDocLen) {
// Negative values and 0 mean "unlimited", mark with -1L
if (maxDocLen <= 0L) {
maxDocLen = -1L;
}
this.maxDocLen = maxDocLen;
return this;
}

/**
* Sets the maximum number length (in chars or bytes, depending on input context).
* The default is 1000.
Expand Down Expand Up @@ -184,25 +220,30 @@ public Builder maxNameLength(final int maxNameLen) {
}

Builder() {
this(DEFAULT_MAX_DEPTH, DEFAULT_MAX_NUM_LEN, DEFAULT_MAX_STRING_LEN, DEFAULT_MAX_NAME_LEN);
this(DEFAULT_MAX_DEPTH, DEFAULT_MAX_DOC_LEN,
DEFAULT_MAX_NUM_LEN, DEFAULT_MAX_STRING_LEN, DEFAULT_MAX_NAME_LEN);
}

Builder(final int maxNestingDepth, final int maxNumLen, final int maxStringLen, final int maxNameLen) {
Builder(final int maxNestingDepth, final long maxDocLen,
final int maxNumLen, final int maxStringLen, final int maxNameLen) {
this.maxNestingDepth = maxNestingDepth;
this.maxDocLen = maxDocLen;
this.maxNumLen = maxNumLen;
this.maxStringLen = maxStringLen;
this.maxNameLen = maxNameLen;
}

Builder(StreamReadConstraints src) {
maxNestingDepth = src._maxNestingDepth;
maxDocLen = src._maxDocLen;
maxNumLen = src._maxNumLen;
maxStringLen = src._maxStringLen;
maxNameLen = src._maxNameLen;
}

public StreamReadConstraints build() {
return new StreamReadConstraints(maxNestingDepth, maxNumLen, maxStringLen, maxNameLen);
return new StreamReadConstraints(maxNestingDepth, maxDocLen,
maxNumLen, maxStringLen, maxNameLen);
}
}

Expand All @@ -213,13 +254,19 @@ public StreamReadConstraints build() {
*/

@Deprecated // since 2.16
protected StreamReadConstraints(final int maxNestingDepth, final int maxNumLen, final int maxStringLen) {
this(maxNestingDepth, maxNumLen, maxStringLen, DEFAULT_MAX_NAME_LEN);
protected StreamReadConstraints(final int maxNestingDepth, final long maxDocLen,
final int maxNumLen, final int maxStringLen) {
this(maxNestingDepth, DEFAULT_MAX_DOC_LEN,
maxNumLen, maxStringLen, DEFAULT_MAX_NAME_LEN);
}

protected StreamReadConstraints(final int maxNestingDepth, final int maxNumLen,
final int maxStringLen, final int maxNameLen) {
/**
* @since 2.16
*/
protected StreamReadConstraints(final int maxNestingDepth, final long maxDocLen,
final int maxNumLen, final int maxStringLen, final int maxNameLen) {
_maxNestingDepth = maxNestingDepth;
_maxDocLen = maxDocLen;
_maxNumLen = maxNumLen;
_maxStringLen = maxStringLen;
_maxNameLen = maxNameLen;
Expand Down Expand Up @@ -261,6 +308,29 @@ public int getMaxNestingDepth() {
return _maxNestingDepth;
}

/**
* Accessor for maximum document length.
* see {@link Builder#maxDocumentLength(long)} for details.
*
* @return Maximum allowed depth
*/
public long getMaxDocumentLength() {
return _maxDocLen;
}

/**
* Convenience method, basically same as:
*<pre>
* getMaxDocumentLength() > 0L
*</pre>
*
* @return {@code True} if this constraints instance has a limit for maximum
* document length to enforce; {@code false} otherwise.
*/
public boolean hasMaxDocumentLength() {
return _maxDocLen > 0L;
}

/**
* Accessor for maximum length of numbers to decode.
* see {@link Builder#maxNumberLength(int)} for details.
Expand Down Expand Up @@ -318,6 +388,31 @@ public void validateNestingDepth(int depth) throws StreamConstraintsException
}
}

/**
* Convenience method that can be used to verify that the
* document length does not exceed the maximum specified by this
* constraints object (if any): if it does, a
* {@link StreamConstraintsException}
* is thrown.
*
* @param len Current length of processed document content
*
* @throws StreamConstraintsException If length exceeds maximum
*
* @since 2.16
*/
public void validateDocumentLength(long len) throws StreamConstraintsException
{
if ((len > _maxDocLen)
// Note: -1L used as marker for "unlimited"
&& (_maxDocLen > 0L)) {
throw _constructException(
"Document length (%d) exceeds the maximum allowed (%d, from %s)",
len, _maxDocLen,
_constrainRef("getMaxDocumentLength"));
}
}

/*
/**********************************************************************
/* Convenience methods for validation, token lengths
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -273,11 +273,14 @@ protected void _loadMoreGuaranteed() throws IOException {
protected boolean _loadMore() throws IOException
{
if (_reader != null) {
final int bufSize = _inputEnd;
_currInputProcessed += bufSize;
_currInputRowStart -= bufSize;
// 06-Sep-2023, tatu: [core#1046] Enforce max doc length limit
streamReadConstraints().validateDocumentLength(_currInputProcessed);

int count = _reader.read(_inputBuffer, 0, _inputBuffer.length);
if (count > 0) {
final int bufSize = _inputEnd;
_currInputProcessed += bufSize;
_currInputRowStart -= bufSize;

// 26-Nov-2015, tatu: Since name-offset requires it too, must offset
// this increase to avoid "moving" name-offset, resulting most likely
Expand All @@ -289,6 +292,7 @@ protected boolean _loadMore() throws IOException

return true;
}
_inputPtr = _inputEnd = 0;
// End of input
_closeInput();
// Should never return 0, so let's fail
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -255,12 +255,14 @@ protected final boolean _loadMore() throws IOException
return false;
}

final int bufSize = _inputEnd;
_currInputProcessed += bufSize;
_currInputRowStart -= bufSize;
// 06-Sep-2023, tatu: [core#1046] Enforce max doc length limit
streamReadConstraints().validateDocumentLength(_currInputProcessed);

int count = _inputStream.read(_inputBuffer, 0, space);
if (count > 0) {
final int bufSize = _inputEnd;

_currInputProcessed += bufSize;
_currInputRowStart -= bufSize;

// 26-Nov-2015, tatu: Since name-offset requires it too, must offset
// this increase to avoid "moving" name-offset, resulting most likely
Expand All @@ -272,6 +274,7 @@ protected final boolean _loadMore() throws IOException

return true;
}
_inputPtr = _inputEnd = 0;
// End of input
_closeInput();
// Should never return 0, so let's fail
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@
* @since 2.14
*/
public class NonBlockingByteBufferJsonParser
extends NonBlockingUtf8JsonParserBase
implements ByteBufferFeeder
extends NonBlockingUtf8JsonParserBase
implements ByteBufferFeeder
{
private ByteBuffer _inputBuffer = ByteBuffer.wrap(NO_BYTES);

Expand Down Expand Up @@ -56,6 +56,9 @@ public void feedInput(final ByteBuffer byteBuffer) throws IOException {
// Time to update pointers first
_currInputProcessed += _origBufferLen;

// 06-Sep-2023, tatu: [core#1046] Enforce max doc length limit
streamReadConstraints().validateDocumentLength(_currInputProcessed);

// Also need to adjust row start, to work as if it extended into the past wrt new buffer
_currInputRowStart = start - (_inputEnd - _currInputRowStart);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ public void feedInput(final byte[] buf, final int start, final int end) throws I
// Time to update pointers first
_currInputProcessed += _origBufferLen;

// 06-Sep-2023, tatu: [core#1046] Enforce max doc length limit
streamReadConstraints().validateDocumentLength(_currInputProcessed);

// Also need to adjust row start, to work as if it extended into the past wrt new buffer
_currInputRowStart = start - (_inputEnd - _currInputRowStart);

Expand Down
Loading

0 comments on commit f511bcd

Please sign in to comment.