From faaf3a184a8b092a916bdaa20b1cdcdf6e0dd9f5 Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Tue, 8 Aug 2023 15:07:49 +0100 Subject: [PATCH 01/11] Create LargeNameReadTest.java --- .../jackson/core/read/LargeNameReadTest.java | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 src/test/java/com/fasterxml/jackson/core/read/LargeNameReadTest.java diff --git a/src/test/java/com/fasterxml/jackson/core/read/LargeNameReadTest.java b/src/test/java/com/fasterxml/jackson/core/read/LargeNameReadTest.java new file mode 100644 index 0000000000..004557d17f --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/core/read/LargeNameReadTest.java @@ -0,0 +1,29 @@ +package com.fasterxml.jackson.core.read; + +import com.fasterxml.jackson.core.BaseTest; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; + +public class LargeNameReadTest extends BaseTest { + + public void testLargeName() throws Exception + { + final String doc = generateJSON(1000); + try (JsonParser jp = createParserUsingStream(doc, "UTF-8")) { + JsonToken jsonToken; + while ((jsonToken = jp.nextToken()) != null) { + + } + } + } + + private String generateJSON(final int nameLen) { + final StringBuilder sb = new StringBuilder(); + sb.append("{\""); + for (int i = 0; i < nameLen; i++) { + sb.append("a"); + } + sb.append("\":\"value\"}"); + return sb.toString(); + } +} From 68426778969cf8778183c4ca112bcfeaae7fe406 Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Tue, 8 Aug 2023 15:19:09 +0100 Subject: [PATCH 02/11] wip --- .../com/fasterxml/jackson/core/read/LargeNameReadTest.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/test/java/com/fasterxml/jackson/core/read/LargeNameReadTest.java b/src/test/java/com/fasterxml/jackson/core/read/LargeNameReadTest.java index 004557d17f..0b38b4382a 100644 --- a/src/test/java/com/fasterxml/jackson/core/read/LargeNameReadTest.java +++ b/src/test/java/com/fasterxml/jackson/core/read/LargeNameReadTest.java @@ -1,15 +1,20 @@ package com.fasterxml.jackson.core.read; import com.fasterxml.jackson.core.BaseTest; +import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.core.StreamReadConstraints; public class LargeNameReadTest extends BaseTest { public void testLargeName() throws Exception { final String doc = generateJSON(1000); - try (JsonParser jp = createParserUsingStream(doc, "UTF-8")) { + final JsonFactory jsonFactory = JsonFactory.builder() + .streamReadConstraints(StreamReadConstraints.builder().maxNameLength(100).build()) + .build(); + try (JsonParser jp = createParserUsingStream(jsonFactory, doc, "UTF-8")) { JsonToken jsonToken; while ((jsonToken = jp.nextToken()) != null) { From 58209c3129be0983f25620b0bc6b0b2fe3c18e4d Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Tue, 8 Aug 2023 15:37:54 +0100 Subject: [PATCH 03/11] add test --- .../core/json/UTF8StreamJsonParser.java | 34 ++++++++++++------- .../jackson/core/read/LargeNameReadTest.java | 17 ++++++++++ 2 files changed, 39 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/fasterxml/jackson/core/json/UTF8StreamJsonParser.java b/src/main/java/com/fasterxml/jackson/core/json/UTF8StreamJsonParser.java index 125e9c530c..8e0aeec704 100644 --- a/src/main/java/com/fasterxml/jackson/core/json/UTF8StreamJsonParser.java +++ b/src/main/java/com/fasterxml/jackson/core/json/UTF8StreamJsonParser.java @@ -1980,7 +1980,7 @@ protected final String parseLongName(int q, final int q2, int q3) throws IOExcep // Nope, no end in sight. Need to grow quad array etc if (qlen >= _quadBuffer.length) { - _quadBuffer = growArrayBy(_quadBuffer, qlen); + _quadBuffer = growArrayWithNameLenCheck(_quadBuffer, qlen); } _quadBuffer[qlen++] = q; q = i; @@ -2014,6 +2014,12 @@ private final String parseName(int q1, int ch, int lastQuadBytes) throws IOExcep return parseEscapedName(_quadBuffer, 0, q1, ch, lastQuadBytes); } + private int[] growArrayWithNameLenCheck(int[] arr, int more) throws StreamConstraintsException { + // the following check will fail if the array is already bigger than is allowed for names + _streamReadConstraints.validateNameLength(arr.length); + return growArrayBy(_quadBuffer, more); + } + private final String parseName(int q1, int q2, int ch, int lastQuadBytes) throws IOException { _quadBuffer[0] = q1; return parseEscapedName(_quadBuffer, 1, q2, ch, lastQuadBytes); @@ -2057,7 +2063,7 @@ protected final String parseEscapedName(int[] quads, int qlen, int currQuad, int // Ok, we'll need room for first byte right away if (currQuadBytes >= 4) { if (qlen >= quads.length) { - _quadBuffer = quads = growArrayBy(quads, quads.length); + _quadBuffer = quads = growArrayWithNameLenCheck(quads, quads.length); } quads[qlen++] = currQuad; currQuad = 0; @@ -2073,7 +2079,7 @@ protected final String parseEscapedName(int[] quads, int qlen, int currQuad, int // need room for middle byte? if (currQuadBytes >= 4) { if (qlen >= quads.length) { - _quadBuffer = quads = growArrayBy(quads, quads.length); + _quadBuffer = quads = growArrayWithNameLenCheck(quads, quads.length); } quads[qlen++] = currQuad; currQuad = 0; @@ -2092,7 +2098,7 @@ protected final String parseEscapedName(int[] quads, int qlen, int currQuad, int currQuad = (currQuad << 8) | ch; } else { if (qlen >= quads.length) { - _quadBuffer = quads = growArrayBy(quads, quads.length); + _quadBuffer = quads = growArrayWithNameLenCheck(quads, quads.length); } quads[qlen++] = currQuad; currQuad = ch; @@ -2108,10 +2114,11 @@ protected final String parseEscapedName(int[] quads, int qlen, int currQuad, int if (currQuadBytes > 0) { if (qlen >= quads.length) { - _quadBuffer = quads = growArrayBy(quads, quads.length); + _quadBuffer = quads = growArrayWithNameLenCheck(quads, quads.length); } quads[qlen++] = _padLastQuad(currQuad, currQuadBytes); } + _streamReadConstraints.validateNameLength(qlen); String name = _symbols.findName(quads, qlen); if (name == null) { name = addName(quads, qlen, currQuadBytes); @@ -2168,7 +2175,7 @@ protected String _handleOddName(int ch) throws IOException currQuad = (currQuad << 8) | ch; } else { if (qlen >= quads.length) { - _quadBuffer = quads = growArrayBy(quads, quads.length); + _quadBuffer = quads = growArrayWithNameLenCheck(quads, quads.length); } quads[qlen++] = currQuad; currQuad = ch; @@ -2188,10 +2195,11 @@ protected String _handleOddName(int ch) throws IOException if (currQuadBytes > 0) { if (qlen >= quads.length) { - _quadBuffer = quads = growArrayBy(quads, quads.length); + _quadBuffer = quads = growArrayWithNameLenCheck(quads, quads.length); } quads[qlen++] = currQuad; } + _streamReadConstraints.validateNameLength(qlen); String name = _symbols.findName(quads, qlen); if (name == null) { name = addName(quads, qlen, currQuadBytes); @@ -2242,7 +2250,7 @@ protected String _parseAposName() throws IOException // Ok, we'll need room for first byte right away if (currQuadBytes >= 4) { if (qlen >= quads.length) { - _quadBuffer = quads = growArrayBy(quads, quads.length); + _quadBuffer = quads = growArrayWithNameLenCheck(quads, quads.length); } quads[qlen++] = currQuad; currQuad = 0; @@ -2258,7 +2266,7 @@ protected String _parseAposName() throws IOException // need room for middle byte? if (currQuadBytes >= 4) { if (qlen >= quads.length) { - _quadBuffer = quads = growArrayBy(quads, quads.length); + _quadBuffer = quads = growArrayWithNameLenCheck(quads, quads.length); } quads[qlen++] = currQuad; currQuad = 0; @@ -2277,7 +2285,7 @@ protected String _parseAposName() throws IOException currQuad = (currQuad << 8) | ch; } else { if (qlen >= quads.length) { - _quadBuffer = quads = growArrayBy(quads, quads.length); + _quadBuffer = quads = growArrayWithNameLenCheck(quads, quads.length); } quads[qlen++] = currQuad; currQuad = ch; @@ -2293,10 +2301,11 @@ protected String _parseAposName() throws IOException if (currQuadBytes > 0) { if (qlen >= quads.length) { - _quadBuffer = quads = growArrayBy(quads, quads.length); + _quadBuffer = quads = growArrayWithNameLenCheck(quads, quads.length); } quads[qlen++] = _padLastQuad(currQuad, currQuadBytes); } + _streamReadConstraints.validateNameLength(qlen); String name = _symbols.findName(quads, qlen); if (name == null) { name = addName(quads, qlen, currQuadBytes); @@ -2358,9 +2367,10 @@ private final String findName(int[] quads, int qlen, int lastQuad, int lastQuadB throws JsonParseException, StreamConstraintsException { if (qlen >= quads.length) { - _quadBuffer = quads = growArrayBy(quads, quads.length); + _quadBuffer = quads = growArrayWithNameLenCheck(quads, quads.length); } quads[qlen++] = _padLastQuad(lastQuad, lastQuadBytes); + _streamReadConstraints.validateNameLength(qlen); String name = _symbols.findName(quads, qlen); if (name == null) { return addName(quads, qlen, lastQuadBytes); diff --git a/src/test/java/com/fasterxml/jackson/core/read/LargeNameReadTest.java b/src/test/java/com/fasterxml/jackson/core/read/LargeNameReadTest.java index 0b38b4382a..dcc5bad087 100644 --- a/src/test/java/com/fasterxml/jackson/core/read/LargeNameReadTest.java +++ b/src/test/java/com/fasterxml/jackson/core/read/LargeNameReadTest.java @@ -5,10 +5,23 @@ import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonToken; import com.fasterxml.jackson.core.StreamReadConstraints; +import com.fasterxml.jackson.core.exc.StreamConstraintsException; public class LargeNameReadTest extends BaseTest { public void testLargeName() throws Exception + { + final String doc = generateJSON(1000); + final JsonFactory jsonFactory = JsonFactory.builder().build(); + try (JsonParser jp = createParserUsingStream(jsonFactory, doc, "UTF-8")) { + JsonToken jsonToken; + while ((jsonToken = jp.nextToken()) != null) { + + } + } + } + + public void testLargeNameWithSmallLimit() throws Exception { final String doc = generateJSON(1000); final JsonFactory jsonFactory = JsonFactory.builder() @@ -19,6 +32,10 @@ public void testLargeName() throws Exception while ((jsonToken = jp.nextToken()) != null) { } + fail("expected StreamConstraintsException"); + } catch (StreamConstraintsException e) { + assertTrue("Unexpected exception message: " + e.getMessage(), + e.getMessage().contains("Name value length")); } } From 73a3623df8841410cd6d3fef0f13d21668984ddd Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Tue, 8 Aug 2023 15:48:10 +0100 Subject: [PATCH 04/11] Update NonBlockingUtf8JsonParserBase.java --- .../async/NonBlockingUtf8JsonParserBase.java | 30 ++++++++++++------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/fasterxml/jackson/core/json/async/NonBlockingUtf8JsonParserBase.java b/src/main/java/com/fasterxml/jackson/core/json/async/NonBlockingUtf8JsonParserBase.java index e581fb663d..f41c65a4a7 100644 --- a/src/main/java/com/fasterxml/jackson/core/json/async/NonBlockingUtf8JsonParserBase.java +++ b/src/main/java/com/fasterxml/jackson/core/json/async/NonBlockingUtf8JsonParserBase.java @@ -2,6 +2,7 @@ import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.core.exc.StreamConstraintsException; import com.fasterxml.jackson.core.io.CharTypes; import com.fasterxml.jackson.core.io.IOContext; import com.fasterxml.jackson.core.json.JsonReadFeature; @@ -2096,7 +2097,7 @@ private final JsonToken _parseEscapedName(int qlen, int currQuad, int currQuadBy continue; } if (qlen >= quads.length) { - _quadBuffer = quads = growArrayBy(quads, quads.length); + _quadBuffer = quads = growArrayWithNameLenCheck(quads, quads.length); } quads[qlen++] = currQuad; currQuad = ch; @@ -2129,7 +2130,7 @@ private final JsonToken _parseEscapedName(int qlen, int currQuad, int currQuadBy // 7-bit ASCII. Gets pretty messy. If this happens often, may // want to use different name canonicalization to avoid these hits. if (qlen >= quads.length) { - _quadBuffer = quads = growArrayBy(quads, quads.length); + _quadBuffer = quads = growArrayWithNameLenCheck(quads, quads.length); } if (ch > 127) { // Ok, we'll need room for first byte right away @@ -2169,12 +2170,13 @@ private final JsonToken _parseEscapedName(int qlen, int currQuad, int currQuadBy if (currQuadBytes > 0) { if (qlen >= quads.length) { - _quadBuffer = quads = growArrayBy(quads, quads.length); + _quadBuffer = quads = growArrayWithNameLenCheck(quads, quads.length); } quads[qlen++] = _padLastQuad(currQuad, currQuadBytes); } else if (qlen == 0) { // rare, but may happen return _fieldComplete(""); } + _streamReadConstraints.validateNameLength(qlen); String name = _symbols.findName(quads, qlen); if (name == null) { name = _addName(quads, qlen, currQuadBytes); @@ -2259,7 +2261,7 @@ private JsonToken _finishUnquotedName(int qlen, int currQuad, int currQuadBytes) currQuad = (currQuad << 8) | ch; } else { if (qlen >= quads.length) { - _quadBuffer = quads = growArrayBy(quads, quads.length); + _quadBuffer = quads = growArrayWithNameLenCheck(quads, quads.length); } quads[qlen++] = currQuad; currQuad = ch; @@ -2269,10 +2271,11 @@ private JsonToken _finishUnquotedName(int qlen, int currQuad, int currQuadBytes) if (currQuadBytes > 0) { if (qlen >= quads.length) { - _quadBuffer = quads = growArrayBy(quads, quads.length); + _quadBuffer = quads = growArrayWithNameLenCheck(quads, quads.length); } quads[qlen++] = currQuad; } + _streamReadConstraints.validateNameLength(qlen); String name = _symbols.findName(quads, qlen); if (name == null) { name = _addName(quads, qlen, currQuadBytes); @@ -2319,7 +2322,7 @@ private JsonToken _finishAposName(int qlen, int currQuad, int currQuadBytes) // Ok, we'll need room for first byte right away if (currQuadBytes >= 4) { if (qlen >= quads.length) { - _quadBuffer = quads = growArrayBy(quads, quads.length); + _quadBuffer = quads = growArrayWithNameLenCheck(quads, quads.length); } quads[qlen++] = currQuad; currQuad = 0; @@ -2335,7 +2338,7 @@ private JsonToken _finishAposName(int qlen, int currQuad, int currQuadBytes) // need room for middle byte? if (currQuadBytes >= 4) { if (qlen >= quads.length) { - _quadBuffer = quads = growArrayBy(quads, quads.length); + _quadBuffer = quads = growArrayWithNameLenCheck(quads, quads.length); } quads[qlen++] = currQuad; currQuad = 0; @@ -2354,7 +2357,7 @@ private JsonToken _finishAposName(int qlen, int currQuad, int currQuadBytes) currQuad = (currQuad << 8) | ch; } else { if (qlen >= quads.length) { - _quadBuffer = quads = growArrayBy(quads, quads.length); + _quadBuffer = quads = growArrayWithNameLenCheck(quads, quads.length); } quads[qlen++] = currQuad; currQuad = ch; @@ -2364,12 +2367,13 @@ private JsonToken _finishAposName(int qlen, int currQuad, int currQuadBytes) if (currQuadBytes > 0) { if (qlen >= quads.length) { - _quadBuffer = quads = growArrayBy(quads, quads.length); + _quadBuffer = quads = growArrayWithNameLenCheck(quads, quads.length); } quads[qlen++] = _padLastQuad(currQuad, currQuadBytes); } else if (qlen == 0) { // rare case but possible return _fieldComplete(""); } + _streamReadConstraints.validateNameLength(qlen); String name = _symbols.findName(quads, qlen); if (name == null) { name = _addName(quads, qlen, currQuadBytes); @@ -2386,7 +2390,7 @@ protected final JsonToken _finishFieldWithEscape() throws IOException return JsonToken.NOT_AVAILABLE; } if (_quadLength >= _quadBuffer.length) { - _quadBuffer = growArrayBy(_quadBuffer, 32); + _quadBuffer = growArrayWithNameLenCheck(_quadBuffer, 32); } int currQuad = _pending32; int currQuadBytes = _pendingBytes; @@ -2429,6 +2433,12 @@ protected final JsonToken _finishFieldWithEscape() throws IOException return _parseEscapedName(_quadLength, currQuad, currQuadBytes); } + private int[] growArrayWithNameLenCheck(int[] arr, int more) throws StreamConstraintsException { + // the following check will fail if the array is already bigger than is allowed for names + _streamReadConstraints.validateNameLength(arr.length); + return growArrayBy(_quadBuffer, more); + } + private int _decodeSplitEscaped(int value, int bytesRead) throws IOException { if (_inputPtr >= _inputEnd) { From 0cb69a2f385a3287df8375703a22b3f44f4ffafb Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Tue, 8 Aug 2023 18:41:25 +0100 Subject: [PATCH 05/11] reader check --- .../core/json/ReaderBasedJsonParser.java | 51 ++++++++++++------- .../jackson/core/read/LargeNameReadTest.java | 18 +++++++ 2 files changed, 50 insertions(+), 19 deletions(-) diff --git a/src/main/java/com/fasterxml/jackson/core/json/ReaderBasedJsonParser.java b/src/main/java/com/fasterxml/jackson/core/json/ReaderBasedJsonParser.java index 22aedb5ae9..54807d7c13 100644 --- a/src/main/java/com/fasterxml/jackson/core/json/ReaderBasedJsonParser.java +++ b/src/main/java/com/fasterxml/jackson/core/json/ReaderBasedJsonParser.java @@ -1848,9 +1848,11 @@ protected final String _parseName() throws IOException int ch = _inputBuffer[ptr]; if (ch < codes.length && codes[ch] != 0) { if (ch == '"') { - int start = _inputPtr; + final int start = _inputPtr; _inputPtr = ptr+1; // to skip the quote - return _symbols.findSymbol(_inputBuffer, start, ptr - start, hash); + final int len = ptr - start; + _streamReadConstraints.validateNameLength(len); + return _symbols.findSymbol(_inputBuffer, start, len, hash); } break; } @@ -1864,7 +1866,9 @@ protected final String _parseName() throws IOException private String _parseName2(int startPtr, int hash, int endChar) throws IOException { - _textBuffer.resetWithShared(_inputBuffer, startPtr, (_inputPtr - startPtr)); + final int initLen = _inputPtr - startPtr; + _streamReadConstraints.validateNameLength(initLen); + _textBuffer.resetWithShared(_inputBuffer, startPtr, initLen); /* Output pointers; calls will also ensure that the buffer is * not shared and has room for at least one more char. @@ -1908,10 +1912,11 @@ private String _parseName2(int startPtr, int hash, int endChar) throws IOExcepti } _textBuffer.setCurrentLength(outPtr); { - TextBuffer tb = _textBuffer; - char[] buf = tb.getTextBuffer(); - int start = tb.getTextOffset(); - int len = tb.size(); + final TextBuffer tb = _textBuffer; + final char[] buf = tb.getTextBuffer(); + final int start = tb.getTextOffset(); + final int len = tb.size(); + _streamReadConstraints.validateNameLength(len); return _symbols.findSymbol(buf, start, len, hash); } } @@ -1962,14 +1967,18 @@ protected String _handleOddName(int i) throws IOException int ch = _inputBuffer[ptr]; if (ch < maxCode) { if (codes[ch] != 0) { - int start = _inputPtr-1; // -1 to bring back first char + final int start = _inputPtr-1; // -1 to bring back first char _inputPtr = ptr; - return _symbols.findSymbol(_inputBuffer, start, ptr - start, hash); + final int len = ptr - start; + _streamReadConstraints.validateNameLength(len); + return _symbols.findSymbol(_inputBuffer, start, len, hash); } } else if (!Character.isJavaIdentifierPart((char) ch)) { - int start = _inputPtr-1; // -1 to bring back first char + final int start = _inputPtr-1; // -1 to bring back first char _inputPtr = ptr; - return _symbols.findSymbol(_inputBuffer, start, ptr - start, hash); + final int len = ptr - start; + _streamReadConstraints.validateNameLength(len); + return _symbols.findSymbol(_inputBuffer, start, len, hash); } hash = (hash * CharsToNameCanonicalizer.HASH_MULT) + ch; ++ptr; @@ -1996,7 +2005,9 @@ protected String _parseAposName() throws IOException if (ch == '\'') { int start = _inputPtr; _inputPtr = ptr+1; // to skip the quote - return _symbols.findSymbol(_inputBuffer, start, ptr - start, hash); + final int len = ptr - start; + _streamReadConstraints.validateNameLength(len); + return _symbols.findSymbol(_inputBuffer, start, len, hash); } if (ch < maxCode && codes[ch] != 0) { break; @@ -2029,7 +2040,7 @@ protected JsonToken _handleOddValue(int i) throws IOException switch (i) { case '\'': /* Allow single quotes? Unlike with regular Strings, we'll eagerly parse - * contents; this so that there'sno need to store information on quote char used. + * contents; this so that there's no need to store information on quote char used. * Also, no separation to fast/slow parsing; we'll just do * one regular (~= slowish) parsing, to keep code simple */ @@ -2129,7 +2140,9 @@ protected JsonToken _handleApos() throws IOException private String _handleOddName2(int startPtr, int hash, int[] codes) throws IOException { - _textBuffer.resetWithShared(_inputBuffer, startPtr, (_inputPtr - startPtr)); + final int initLen = _inputPtr - startPtr; + _streamReadConstraints.validateNameLength(initLen); + _textBuffer.resetWithShared(_inputBuffer, startPtr, initLen); char[] outBuf = _textBuffer.getCurrentSegment(); int outPtr = _textBuffer.getCurrentSegmentSize(); final int maxCode = codes.length; @@ -2162,11 +2175,11 @@ private String _handleOddName2(int startPtr, int hash, int[] codes) throws IOExc } _textBuffer.setCurrentLength(outPtr); { - TextBuffer tb = _textBuffer; - char[] buf = tb.getTextBuffer(); - int start = tb.getTextOffset(); - int len = tb.size(); - + final TextBuffer tb = _textBuffer; + final char[] buf = tb.getTextBuffer(); + final int start = tb.getTextOffset(); + final int len = tb.size(); + _streamReadConstraints.validateNameLength(len); return _symbols.findSymbol(buf, start, len, hash); } } diff --git a/src/test/java/com/fasterxml/jackson/core/read/LargeNameReadTest.java b/src/test/java/com/fasterxml/jackson/core/read/LargeNameReadTest.java index dcc5bad087..c2dae57deb 100644 --- a/src/test/java/com/fasterxml/jackson/core/read/LargeNameReadTest.java +++ b/src/test/java/com/fasterxml/jackson/core/read/LargeNameReadTest.java @@ -39,6 +39,24 @@ public void testLargeNameWithSmallLimit() throws Exception } } + public void testReaderLargeNameWithSmallLimit() throws Exception + { + final String doc = generateJSON(1000); + final JsonFactory jsonFactory = JsonFactory.builder() + .streamReadConstraints(StreamReadConstraints.builder().maxNameLength(100).build()) + .build(); + try (JsonParser jp = createParserUsingReader(jsonFactory, doc)) { + JsonToken jsonToken; + while ((jsonToken = jp.nextToken()) != null) { + System.out.println(jsonToken); + } + fail("expected StreamConstraintsException"); + } catch (StreamConstraintsException e) { + assertTrue("Unexpected exception message: " + e.getMessage(), + e.getMessage().contains("Name value length")); + } + } + private String generateJSON(final int nameLen) { final StringBuilder sb = new StringBuilder(); sb.append("{\""); From 3dc72871552b8c4cc89434c4cb0760792b5b4003 Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Thu, 10 Aug 2023 15:27:28 +0100 Subject: [PATCH 06/11] non blocking test --- .../jackson/core/read/LargeNameReadTest.java | 41 ++++++++++++++----- 1 file changed, 30 insertions(+), 11 deletions(-) diff --git a/src/test/java/com/fasterxml/jackson/core/read/LargeNameReadTest.java b/src/test/java/com/fasterxml/jackson/core/read/LargeNameReadTest.java index c2dae57deb..14c6e5d544 100644 --- a/src/test/java/com/fasterxml/jackson/core/read/LargeNameReadTest.java +++ b/src/test/java/com/fasterxml/jackson/core/read/LargeNameReadTest.java @@ -6,6 +6,10 @@ import com.fasterxml.jackson.core.JsonToken; import com.fasterxml.jackson.core.StreamReadConstraints; import com.fasterxml.jackson.core.exc.StreamConstraintsException; +import com.fasterxml.jackson.core.json.async.NonBlockingJsonParser; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; public class LargeNameReadTest extends BaseTest { @@ -14,10 +18,7 @@ public void testLargeName() throws Exception final String doc = generateJSON(1000); final JsonFactory jsonFactory = JsonFactory.builder().build(); try (JsonParser jp = createParserUsingStream(jsonFactory, doc, "UTF-8")) { - JsonToken jsonToken; - while ((jsonToken = jp.nextToken()) != null) { - - } + consumeTokens(jp); } } @@ -28,10 +29,24 @@ public void testLargeNameWithSmallLimit() throws Exception .streamReadConstraints(StreamReadConstraints.builder().maxNameLength(100).build()) .build(); try (JsonParser jp = createParserUsingStream(jsonFactory, doc, "UTF-8")) { - JsonToken jsonToken; - while ((jsonToken = jp.nextToken()) != null) { + consumeTokens(jp); + fail("expected StreamConstraintsException"); + } catch (StreamConstraintsException e) { + assertTrue("Unexpected exception message: " + e.getMessage(), + e.getMessage().contains("Name value length")); + } + } - } + public void testAsyncLargeNameWithSmallLimit() throws Exception + { + final byte[] doc = generateJSON(1000).getBytes(StandardCharsets.UTF_8); + final JsonFactory jsonFactory = JsonFactory.builder() + .streamReadConstraints(StreamReadConstraints.builder().maxNameLength(100).build()) + .build(); + + try (NonBlockingJsonParser jp = (NonBlockingJsonParser) jsonFactory.createNonBlockingByteArrayParser()) { + jp.feedInput(doc, 0, doc.length); + consumeTokens(jp); fail("expected StreamConstraintsException"); } catch (StreamConstraintsException e) { assertTrue("Unexpected exception message: " + e.getMessage(), @@ -46,10 +61,7 @@ public void testReaderLargeNameWithSmallLimit() throws Exception .streamReadConstraints(StreamReadConstraints.builder().maxNameLength(100).build()) .build(); try (JsonParser jp = createParserUsingReader(jsonFactory, doc)) { - JsonToken jsonToken; - while ((jsonToken = jp.nextToken()) != null) { - System.out.println(jsonToken); - } + consumeTokens(jp); fail("expected StreamConstraintsException"); } catch (StreamConstraintsException e) { assertTrue("Unexpected exception message: " + e.getMessage(), @@ -57,6 +69,13 @@ public void testReaderLargeNameWithSmallLimit() throws Exception } } + private void consumeTokens(JsonParser jp) throws IOException { + JsonToken jsonToken; + while ((jsonToken = jp.nextToken()) != null) { + + } + } + private String generateJSON(final int nameLen) { final StringBuilder sb = new StringBuilder(); sb.append("{\""); From 3f0c5bc727a3b3c1b05e5f32d97647d4820ea8da Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Wed, 23 Aug 2023 13:37:43 +0100 Subject: [PATCH 07/11] change name check --- .../jackson/core/json/UTF8StreamJsonParser.java | 10 +++++----- .../core/json/async/NonBlockingUtf8JsonParserBase.java | 8 ++++---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/fasterxml/jackson/core/json/UTF8StreamJsonParser.java b/src/main/java/com/fasterxml/jackson/core/json/UTF8StreamJsonParser.java index 8e0aeec704..ecfb7dedce 100644 --- a/src/main/java/com/fasterxml/jackson/core/json/UTF8StreamJsonParser.java +++ b/src/main/java/com/fasterxml/jackson/core/json/UTF8StreamJsonParser.java @@ -2016,7 +2016,7 @@ private final String parseName(int q1, int ch, int lastQuadBytes) throws IOExcep private int[] growArrayWithNameLenCheck(int[] arr, int more) throws StreamConstraintsException { // the following check will fail if the array is already bigger than is allowed for names - _streamReadConstraints.validateNameLength(arr.length); + _streamReadConstraints.validateNameLength(arr.length << 2); return growArrayBy(_quadBuffer, more); } @@ -2118,7 +2118,7 @@ protected final String parseEscapedName(int[] quads, int qlen, int currQuad, int } quads[qlen++] = _padLastQuad(currQuad, currQuadBytes); } - _streamReadConstraints.validateNameLength(qlen); + _streamReadConstraints.validateNameLength(qlen << 2); String name = _symbols.findName(quads, qlen); if (name == null) { name = addName(quads, qlen, currQuadBytes); @@ -2199,7 +2199,7 @@ protected String _handleOddName(int ch) throws IOException } quads[qlen++] = currQuad; } - _streamReadConstraints.validateNameLength(qlen); + _streamReadConstraints.validateNameLength(qlen << 2); String name = _symbols.findName(quads, qlen); if (name == null) { name = addName(quads, qlen, currQuadBytes); @@ -2305,7 +2305,7 @@ protected String _parseAposName() throws IOException } quads[qlen++] = _padLastQuad(currQuad, currQuadBytes); } - _streamReadConstraints.validateNameLength(qlen); + _streamReadConstraints.validateNameLength(qlen << 2); String name = _symbols.findName(quads, qlen); if (name == null) { name = addName(quads, qlen, currQuadBytes); @@ -2370,7 +2370,7 @@ private final String findName(int[] quads, int qlen, int lastQuad, int lastQuadB _quadBuffer = quads = growArrayWithNameLenCheck(quads, quads.length); } quads[qlen++] = _padLastQuad(lastQuad, lastQuadBytes); - _streamReadConstraints.validateNameLength(qlen); + _streamReadConstraints.validateNameLength(qlen << 2); String name = _symbols.findName(quads, qlen); if (name == null) { return addName(quads, qlen, lastQuadBytes); diff --git a/src/main/java/com/fasterxml/jackson/core/json/async/NonBlockingUtf8JsonParserBase.java b/src/main/java/com/fasterxml/jackson/core/json/async/NonBlockingUtf8JsonParserBase.java index f41c65a4a7..4c5c328ef9 100644 --- a/src/main/java/com/fasterxml/jackson/core/json/async/NonBlockingUtf8JsonParserBase.java +++ b/src/main/java/com/fasterxml/jackson/core/json/async/NonBlockingUtf8JsonParserBase.java @@ -2176,7 +2176,7 @@ private final JsonToken _parseEscapedName(int qlen, int currQuad, int currQuadBy } else if (qlen == 0) { // rare, but may happen return _fieldComplete(""); } - _streamReadConstraints.validateNameLength(qlen); + _streamReadConstraints.validateNameLength(qlen << 2); String name = _symbols.findName(quads, qlen); if (name == null) { name = _addName(quads, qlen, currQuadBytes); @@ -2275,7 +2275,7 @@ private JsonToken _finishUnquotedName(int qlen, int currQuad, int currQuadBytes) } quads[qlen++] = currQuad; } - _streamReadConstraints.validateNameLength(qlen); + _streamReadConstraints.validateNameLength(qlen << 2); String name = _symbols.findName(quads, qlen); if (name == null) { name = _addName(quads, qlen, currQuadBytes); @@ -2373,7 +2373,7 @@ private JsonToken _finishAposName(int qlen, int currQuad, int currQuadBytes) } else if (qlen == 0) { // rare case but possible return _fieldComplete(""); } - _streamReadConstraints.validateNameLength(qlen); + _streamReadConstraints.validateNameLength(qlen << 2); String name = _symbols.findName(quads, qlen); if (name == null) { name = _addName(quads, qlen, currQuadBytes); @@ -2435,7 +2435,7 @@ protected final JsonToken _finishFieldWithEscape() throws IOException private int[] growArrayWithNameLenCheck(int[] arr, int more) throws StreamConstraintsException { // the following check will fail if the array is already bigger than is allowed for names - _streamReadConstraints.validateNameLength(arr.length); + _streamReadConstraints.validateNameLength(arr.length << 2); return growArrayBy(_quadBuffer, more); } From d16b08c02da1798cd46d8f97c1fceac63ce41943 Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Thu, 24 Aug 2023 08:34:03 +0100 Subject: [PATCH 08/11] move some reader checks --- .../core/json/ReaderBasedJsonParser.java | 24 +++++-------------- .../core/sym/CharsToNameCanonicalizer.java | 1 + 2 files changed, 7 insertions(+), 18 deletions(-) diff --git a/src/main/java/com/fasterxml/jackson/core/json/ReaderBasedJsonParser.java b/src/main/java/com/fasterxml/jackson/core/json/ReaderBasedJsonParser.java index 54807d7c13..bcd27170f3 100644 --- a/src/main/java/com/fasterxml/jackson/core/json/ReaderBasedJsonParser.java +++ b/src/main/java/com/fasterxml/jackson/core/json/ReaderBasedJsonParser.java @@ -1850,9 +1850,7 @@ protected final String _parseName() throws IOException if (ch == '"') { final int start = _inputPtr; _inputPtr = ptr+1; // to skip the quote - final int len = ptr - start; - _streamReadConstraints.validateNameLength(len); - return _symbols.findSymbol(_inputBuffer, start, len, hash); + return _symbols.findSymbol(_inputBuffer, start, ptr - start, hash); } break; } @@ -1915,9 +1913,7 @@ private String _parseName2(int startPtr, int hash, int endChar) throws IOExcepti final TextBuffer tb = _textBuffer; final char[] buf = tb.getTextBuffer(); final int start = tb.getTextOffset(); - final int len = tb.size(); - _streamReadConstraints.validateNameLength(len); - return _symbols.findSymbol(buf, start, len, hash); + return _symbols.findSymbol(buf, start, tb.size(), hash); } } @@ -1969,16 +1965,12 @@ protected String _handleOddName(int i) throws IOException if (codes[ch] != 0) { final int start = _inputPtr-1; // -1 to bring back first char _inputPtr = ptr; - final int len = ptr - start; - _streamReadConstraints.validateNameLength(len); - return _symbols.findSymbol(_inputBuffer, start, len, hash); + return _symbols.findSymbol(_inputBuffer, start, ptr - start, hash); } } else if (!Character.isJavaIdentifierPart((char) ch)) { final int start = _inputPtr-1; // -1 to bring back first char _inputPtr = ptr; - final int len = ptr - start; - _streamReadConstraints.validateNameLength(len); - return _symbols.findSymbol(_inputBuffer, start, len, hash); + return _symbols.findSymbol(_inputBuffer, start, ptr - start, hash); } hash = (hash * CharsToNameCanonicalizer.HASH_MULT) + ch; ++ptr; @@ -2005,9 +1997,7 @@ protected String _parseAposName() throws IOException if (ch == '\'') { int start = _inputPtr; _inputPtr = ptr+1; // to skip the quote - final int len = ptr - start; - _streamReadConstraints.validateNameLength(len); - return _symbols.findSymbol(_inputBuffer, start, len, hash); + return _symbols.findSymbol(_inputBuffer, start, ptr - start, hash); } if (ch < maxCode && codes[ch] != 0) { break; @@ -2178,9 +2168,7 @@ private String _handleOddName2(int startPtr, int hash, int[] codes) throws IOExc final TextBuffer tb = _textBuffer; final char[] buf = tb.getTextBuffer(); final int start = tb.getTextOffset(); - final int len = tb.size(); - _streamReadConstraints.validateNameLength(len); - return _symbols.findSymbol(buf, start, len, hash); + return _symbols.findSymbol(buf, start, tb.size(), hash); } } diff --git a/src/main/java/com/fasterxml/jackson/core/sym/CharsToNameCanonicalizer.java b/src/main/java/com/fasterxml/jackson/core/sym/CharsToNameCanonicalizer.java index acaf74574d..d5f2f5fbf3 100644 --- a/src/main/java/com/fasterxml/jackson/core/sym/CharsToNameCanonicalizer.java +++ b/src/main/java/com/fasterxml/jackson/core/sym/CharsToNameCanonicalizer.java @@ -499,6 +499,7 @@ public String findSymbol(char[] buffer, int start, int len, int h) throws IOExce if (len < 1) { // empty Strings are simplest to handle up front return ""; } + _streamReadConstraints.validateNameLength(len); if (!_canonicalize) { // [JACKSON-259] return new String(buffer, start, len); } From f5bc2b4baa13329fd124326a489ce209a5f3145e Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Sun, 27 Aug 2023 20:26:02 +0100 Subject: [PATCH 09/11] remove some checks --- .../jackson/core/json/ReaderBasedJsonParser.java | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/fasterxml/jackson/core/json/ReaderBasedJsonParser.java b/src/main/java/com/fasterxml/jackson/core/json/ReaderBasedJsonParser.java index bcd27170f3..92907fbd7b 100644 --- a/src/main/java/com/fasterxml/jackson/core/json/ReaderBasedJsonParser.java +++ b/src/main/java/com/fasterxml/jackson/core/json/ReaderBasedJsonParser.java @@ -1864,9 +1864,7 @@ protected final String _parseName() throws IOException private String _parseName2(int startPtr, int hash, int endChar) throws IOException { - final int initLen = _inputPtr - startPtr; - _streamReadConstraints.validateNameLength(initLen); - _textBuffer.resetWithShared(_inputBuffer, startPtr, initLen); + _textBuffer.resetWithShared(_inputBuffer, startPtr, _inputPtr - startPtr); /* Output pointers; calls will also ensure that the buffer is * not shared and has room for at least one more char. @@ -2130,9 +2128,7 @@ protected JsonToken _handleApos() throws IOException private String _handleOddName2(int startPtr, int hash, int[] codes) throws IOException { - final int initLen = _inputPtr - startPtr; - _streamReadConstraints.validateNameLength(initLen); - _textBuffer.resetWithShared(_inputBuffer, startPtr, initLen); + _textBuffer.resetWithShared(_inputBuffer, startPtr, _inputPtr - startPtr); char[] outBuf = _textBuffer.getCurrentSegment(); int outPtr = _textBuffer.getCurrentSegmentSize(); final int maxCode = codes.length; From 7788d49bef67f279934dc61ce7caefe56ced33c3 Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Sun, 27 Aug 2023 20:34:11 +0100 Subject: [PATCH 10/11] move name len check (due to review comment) --- .../jackson/core/json/async/NonBlockingJsonParserBase.java | 3 ++- .../jackson/core/json/async/NonBlockingUtf8JsonParserBase.java | 3 --- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/fasterxml/jackson/core/json/async/NonBlockingJsonParserBase.java b/src/main/java/com/fasterxml/jackson/core/json/async/NonBlockingJsonParserBase.java index a5469f5037..4eeb24ee95 100644 --- a/src/main/java/com/fasterxml/jackson/core/json/async/NonBlockingJsonParserBase.java +++ b/src/main/java/com/fasterxml/jackson/core/json/async/NonBlockingJsonParserBase.java @@ -696,7 +696,8 @@ protected final String _addName(int[] quads, int qlen, int lastQuadBytes) * (as well as error reporting for unescaped control chars) */ // 4 bytes per quad, except last one maybe less - int byteLen = (qlen << 2) - 4 + lastQuadBytes; + final int byteLen = (qlen << 2) - 4 + lastQuadBytes; + _streamReadConstraints.validateNameLength(byteLen); /* And last one is not correctly aligned (leading zero bytes instead * need to shift a bit, instead of trailing). Only need to shift it diff --git a/src/main/java/com/fasterxml/jackson/core/json/async/NonBlockingUtf8JsonParserBase.java b/src/main/java/com/fasterxml/jackson/core/json/async/NonBlockingUtf8JsonParserBase.java index 4c5c328ef9..4ceee7b603 100644 --- a/src/main/java/com/fasterxml/jackson/core/json/async/NonBlockingUtf8JsonParserBase.java +++ b/src/main/java/com/fasterxml/jackson/core/json/async/NonBlockingUtf8JsonParserBase.java @@ -2176,7 +2176,6 @@ private final JsonToken _parseEscapedName(int qlen, int currQuad, int currQuadBy } else if (qlen == 0) { // rare, but may happen return _fieldComplete(""); } - _streamReadConstraints.validateNameLength(qlen << 2); String name = _symbols.findName(quads, qlen); if (name == null) { name = _addName(quads, qlen, currQuadBytes); @@ -2275,7 +2274,6 @@ private JsonToken _finishUnquotedName(int qlen, int currQuad, int currQuadBytes) } quads[qlen++] = currQuad; } - _streamReadConstraints.validateNameLength(qlen << 2); String name = _symbols.findName(quads, qlen); if (name == null) { name = _addName(quads, qlen, currQuadBytes); @@ -2373,7 +2371,6 @@ private JsonToken _finishAposName(int qlen, int currQuad, int currQuadBytes) } else if (qlen == 0) { // rare case but possible return _fieldComplete(""); } - _streamReadConstraints.validateNameLength(qlen << 2); String name = _symbols.findName(quads, qlen); if (name == null) { name = _addName(quads, qlen, currQuadBytes); From 1d11c4ab9677290bb206ed96cc122bb95502110d Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Mon, 28 Aug 2023 08:57:34 +0100 Subject: [PATCH 11/11] Update UTF8StreamJsonParser.java --- .../fasterxml/jackson/core/json/UTF8StreamJsonParser.java | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/fasterxml/jackson/core/json/UTF8StreamJsonParser.java b/src/main/java/com/fasterxml/jackson/core/json/UTF8StreamJsonParser.java index ecfb7dedce..f1750e07c6 100644 --- a/src/main/java/com/fasterxml/jackson/core/json/UTF8StreamJsonParser.java +++ b/src/main/java/com/fasterxml/jackson/core/json/UTF8StreamJsonParser.java @@ -2118,7 +2118,6 @@ protected final String parseEscapedName(int[] quads, int qlen, int currQuad, int } quads[qlen++] = _padLastQuad(currQuad, currQuadBytes); } - _streamReadConstraints.validateNameLength(qlen << 2); String name = _symbols.findName(quads, qlen); if (name == null) { name = addName(quads, qlen, currQuadBytes); @@ -2199,7 +2198,6 @@ protected String _handleOddName(int ch) throws IOException } quads[qlen++] = currQuad; } - _streamReadConstraints.validateNameLength(qlen << 2); String name = _symbols.findName(quads, qlen); if (name == null) { name = addName(quads, qlen, currQuadBytes); @@ -2305,7 +2303,6 @@ protected String _parseAposName() throws IOException } quads[qlen++] = _padLastQuad(currQuad, currQuadBytes); } - _streamReadConstraints.validateNameLength(qlen << 2); String name = _symbols.findName(quads, qlen); if (name == null) { name = addName(quads, qlen, currQuadBytes); @@ -2370,7 +2367,6 @@ private final String findName(int[] quads, int qlen, int lastQuad, int lastQuadB _quadBuffer = quads = growArrayWithNameLenCheck(quads, quads.length); } quads[qlen++] = _padLastQuad(lastQuad, lastQuadBytes); - _streamReadConstraints.validateNameLength(qlen << 2); String name = _symbols.findName(quads, qlen); if (name == null) { return addName(quads, qlen, lastQuadBytes); @@ -2391,7 +2387,8 @@ private final String addName(int[] quads, int qlen, int lastQuadBytes) * (as well as error reporting for unescaped control chars) */ // 4 bytes per quad, except last one maybe less - int byteLen = (qlen << 2) - 4 + lastQuadBytes; + final int byteLen = (qlen << 2) - 4 + lastQuadBytes; + _streamReadConstraints.validateNameLength(byteLen); /* And last one is not correctly aligned (leading zero bytes instead * need to shift a bit, instead of trailing). Only need to shift it