Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add "max-doc-len" limit, enforcement #1101

Merged
merged 6 commits into from
Sep 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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