Skip to content

Commit 87683e3

Browse files
committed
Fix #268
1 parent 2bba4e6 commit 87683e3

File tree

3 files changed

+80
-6
lines changed

3 files changed

+80
-6
lines changed

release-notes/VERSION-2.x

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ Modules:
2828
(7-bit encoded)
2929
#266: (smile) ArrayIndexOutOfBoundsException in SmileParser._decodeShortUnicodeValue()
3030
(reported by Fabian M)
31+
#268: (smile) Handle sequence of Smile header markers without recursion
32+
(reported by Fabian M)
3133

3234
2.12.2 (03-Mar-2021)
3335

smile/src/main/java/com/fasterxml/jackson/dataformat/smile/SmileParser.java

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -452,13 +452,14 @@ public JsonToken nextToken() throws IOException
452452
}
453453
if (typeBits == 0x1A) { // == 0x3A == ':' -> possibly header signature for next chunk?
454454
if (handleSignature(false, false)) {
455-
/* Ok, now; end-marker and header both imply doc boundary and a
456-
* 'null token'; but if both are seen, they are collapsed.
457-
* We can check this by looking at current token; if it's null,
458-
* need to get non-null token
459-
*/
455+
// Ok, now; end-marker and header both imply doc boundary and a
456+
// 'null token'; but if both are seen, they are collapsed.
457+
// We can check this by looking at current token; if it's null,
458+
// need to get non-null token
459+
// 30-Mar-2021, tatu: [dataformats-binary#268] Let's verify we
460+
// handle repeated back-to-back headers separately
460461
if (_currToken == null) {
461-
return nextToken();
462+
return _nextAfterHeader();
462463
}
463464
return (_currToken = null);
464465
}
@@ -529,6 +530,28 @@ public JsonToken nextToken() throws IOException
529530
return null;
530531
}
531532

533+
// Helper method called in situations where Smile Header was encountered
534+
// and "current token" is `null`. This can occur both right after document-end
535+
// marker (normal situation) and immediately at the beginning of document
536+
// (repeated header markers). Normally we'll want to find the real next token
537+
// but will not want to do infinite recursion for abnormal case of a very long
538+
// sequence of repeated header markers. To guard against that, only call
539+
// recursively if we know next token cannot be header; checking that is simple
540+
// enough
541+
//
542+
// @since 2.12.3
543+
private JsonToken _nextAfterHeader() throws IOException
544+
{
545+
if ((_inputPtr < _inputEnd) || _loadMore()) {
546+
if (_inputBuffer[_inputPtr] == SmileConstants.HEADER_BYTE_1) {
547+
// danger zone; just set and return null token
548+
return (_currToken = null);
549+
}
550+
}
551+
// Otherwise safe enough to do recursion
552+
return nextToken();
553+
}
554+
532555
private final JsonToken _handleSharedString(int index) throws IOException
533556
{
534557
if (index >= _seenStringValueCount) {
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package com.fasterxml.jackson.dataformat.smile.fuzz;
2+
3+
import java.io.ByteArrayOutputStream;
4+
5+
import com.fasterxml.jackson.core.JsonParser;
6+
import com.fasterxml.jackson.core.JsonToken;
7+
import com.fasterxml.jackson.databind.ObjectMapper;
8+
import com.fasterxml.jackson.dataformat.smile.BaseTestForSmile;
9+
10+
// for [dataformats-binary#268]
11+
public class Fuzz32665RepeatedHeaderTest extends BaseTestForSmile
12+
{
13+
private final ObjectMapper MAPPER = smileMapper();
14+
15+
// for [dataformats-binary#268]
16+
public void testLongRepeatedHeaders() throws Exception
17+
{
18+
ByteArrayOutputStream bytes = new ByteArrayOutputStream(16001);
19+
for (int i = 0; i < 10; ++i) {
20+
// repeat Smile header 10 times
21+
bytes.write(0x3A);
22+
bytes.write(0x29);
23+
bytes.write(0x0A);
24+
bytes.write(0x00);
25+
}
26+
27+
// and then append "empty String" marker for funsies
28+
bytes.write(0x20);
29+
30+
final byte[] DOC = bytes.toByteArray();
31+
32+
try (JsonParser p = MAPPER.createParser(DOC)) {
33+
// Ideally would get 9 nulls but looks like at the beginning of stream
34+
// it will be one less (not so later on). Good enough given that there is
35+
// no real definition of handling here.
36+
for (int i = 0; i < 8; ++i) {
37+
JsonToken t = p.nextToken();
38+
if (t != null) {
39+
fail("Failed at token #"+i+"; expected `null`, got: "+t);
40+
}
41+
}
42+
// and finally, empty String
43+
assertToken(JsonToken.VALUE_STRING, p.nextToken());
44+
45+
// and then the "real" end of input
46+
assertNull(p.nextToken());
47+
}
48+
}
49+
}

0 commit comments

Comments
 (0)