Skip to content

Commit b714f0e

Browse files
committed
Fix #266 (array index out of bounds in _decodeShortUnicodeValue() of SmileParser)
1 parent a831f19 commit b714f0e

File tree

6 files changed

+109
-40
lines changed

6 files changed

+109
-40
lines changed

release-notes/CREDITS-2.x

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,8 @@ Fabian Meumertzheim (fmeum@github)
178178
(2.12.3)
179179
* Reported #263 (smile) Handle invalid chunked-binary-format length gracefully
180180
(2.12.3)
181+
* Reported #266: (smile) ArrayIndexOutOfBoundsException in SmileParser._decodeShortUnicodeValue()
182+
(2.12.3)
181183

182184
(jhhladky@github)
183185

release-notes/VERSION-2.x

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,12 @@ Modules:
2222
(reported by Fabian M)
2323
#261 (cbor) CBORParser need to validate zero-length byte[] for BigInteger
2424
(reported by Fabian M)
25-
#263 (smile) Handle invalid chunked-binary-format length gracefully
25+
#263: (smile) Handle invalid chunked-binary-format length gracefully
2626
(reported by Fabian M)
27-
#265 (smile) Allocate byte[] lazily for longer Smile binary data payloads
27+
#265: (smile) Allocate byte[] lazily for longer Smile binary data payloads
2828
(7-bit encoded)
29+
#266: (smile) ArrayIndexOutOfBoundsException in SmileParser._decodeShortUnicodeValue()
30+
(reported by Fabian M)
2931

3032
2.12.2 (03-Mar-2021)
3133

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

Lines changed: 53 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -335,7 +335,6 @@ protected final int _tryToLoadToHaveAtLeast(int minAvailable) throws IOException
335335
return 0;
336336
}
337337

338-
@SuppressWarnings("deprecation")
339338
@Override
340339
protected void _closeInput() throws IOException
341340
{
@@ -391,7 +390,7 @@ protected void _releaseBuffers2()
391390
/* JsonParser impl
392391
/**********************************************************
393392
*/
394-
393+
395394
@Override
396395
public JsonToken nextToken() throws IOException
397396
{
@@ -2311,44 +2310,53 @@ protected final String _decodeShortAsciiValue(int len) throws IOException
23112310
return _textBuffer.setCurrentAndReturn(len);
23122311
}
23132312

2314-
protected final String _decodeShortUnicodeValue(int len) throws IOException
2313+
protected final String _decodeShortUnicodeValue(final int byteLen) throws IOException
23152314
{
2316-
if ((_inputEnd - _inputPtr) < len) {
2317-
_loadToHaveAtLeast(len);
2315+
if ((_inputEnd - _inputPtr) < byteLen) {
2316+
_loadToHaveAtLeast(byteLen);
23182317
}
23192318
int outPtr = 0;
23202319
char[] outBuf = _textBuffer.emptyAndGetCurrentSegment();
23212320
int inPtr = _inputPtr;
2322-
_inputPtr += len;
2321+
_inputPtr += byteLen;
23232322
final int[] codes = SmileConstants.sUtf8UnitLengths;
23242323
final byte[] inputBuf = _inputBuffer;
2325-
for (int end = inPtr + len; inPtr < end; ) {
2326-
int i = inputBuf[inPtr++] & 0xFF;
2327-
int code = codes[i];
2328-
if (code != 0) {
2329-
// trickiest one, need surrogate handling
2330-
switch (code) {
2331-
case 1:
2332-
i = ((i & 0x1F) << 6) | (inputBuf[inPtr++] & 0x3F);
2333-
break;
2334-
case 2:
2335-
i = ((i & 0x0F) << 12)
2336-
| ((inputBuf[inPtr++] & 0x3F) << 6)
2337-
| (inputBuf[inPtr++] & 0x3F);
2338-
break;
2339-
case 3:
2340-
i = ((i & 0x07) << 18)
2341-
| ((inputBuf[inPtr++] & 0x3F) << 12)
2342-
| ((inputBuf[inPtr++] & 0x3F) << 6)
2343-
| (inputBuf[inPtr++] & 0x3F);
2344-
// note: this is the codepoint value; need to split, too
2345-
i -= 0x10000;
2346-
outBuf[outPtr++] = (char) (0xD800 | (i >> 10));
2347-
i = 0xDC00 | (i & 0x3FF);
2348-
break;
2349-
default: // invalid
2350-
_reportError("Invalid byte "+Integer.toHexString(i)+" in short Unicode text block");
2351-
}
2324+
for (int end = inPtr + byteLen; inPtr < end; ) {
2325+
int i = inputBuf[inPtr++];
2326+
if (i >= 0) {
2327+
outBuf[outPtr++] = (char) i;
2328+
continue;
2329+
}
2330+
i &= 0xFF;
2331+
final int unitLen = codes[i];
2332+
if ((inPtr + unitLen) > end) {
2333+
// Last -1 to compensate for byte that was read:
2334+
final int firstCharOffset = byteLen - (end - inPtr) - 1;
2335+
return _reportTruncatedUTF8InString(byteLen, firstCharOffset, i, unitLen);
2336+
}
2337+
int i2 = inputBuf[inPtr++] & 0x3F;
2338+
2339+
switch (unitLen) {
2340+
case 1:
2341+
i = ((i & 0x1F) << 6) | i2;
2342+
break;
2343+
case 2:
2344+
i = ((i & 0x0F) << 12)
2345+
| (i2 << 6)
2346+
| (inputBuf[inPtr++] & 0x3F);
2347+
break;
2348+
case 3:// trickiest one, need surrogate handling
2349+
i = ((i & 0x07) << 18)
2350+
| (i2 << 12)
2351+
| ((inputBuf[inPtr++] & 0x3F) << 6)
2352+
| (inputBuf[inPtr++] & 0x3F);
2353+
// note: this is the codepoint value; need to split, too
2354+
i -= 0x10000;
2355+
outBuf[outPtr++] = (char) (0xD800 | (i >> 10));
2356+
i = 0xDC00 | (i & 0x3FF);
2357+
break;
2358+
default: // invalid
2359+
_reportError("Invalid byte "+Integer.toHexString(i)+" in short Unicode text block");
23522360
}
23532361
outBuf[outPtr++] = (char) i;
23542362
}
@@ -2948,7 +2956,18 @@ protected void _reportIncompleteBinaryRead7Bit(int expLen, int actLen)
29482956
" for Binary value (7-bit): expected %d payload bytes (from %d encoded), only decoded %d",
29492957
expLen, encodedLen, actLen), currentToken());
29502958
}
2951-
2959+
2960+
// @since 2.12.3
2961+
protected String _reportTruncatedUTF8InString(int strLenBytes, int truncatedCharOffset,
2962+
int firstUTFByteValue, int bytesExpected)
2963+
throws IOException
2964+
{
2965+
throw _constructError(String.format(
2966+
"Truncated UTF-8 character in Short Unicode String value (%d bytes): "
2967+
+"byte 0x%02X at offset #%d indicated %d more bytes needed",
2968+
strLenBytes, firstUTFByteValue, truncatedCharOffset, bytesExpected));
2969+
}
2970+
29522971
/*
29532972
/**********************************************************
29542973
/* Internal methods, other

smile/src/test/java/com/fasterxml/jackson/dataformat/smile/BaseTestForSmile.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -262,9 +262,11 @@ protected byte[] readResource(String ref)
262262
final byte[] buf = new byte[4000];
263263

264264
try (InputStream in = getClass().getResourceAsStream(ref)) {
265-
int len;
266-
while ((len = in.read(buf)) > 0) {
267-
bytes.write(buf, 0, len);
265+
if (in != null) {
266+
int len;
267+
while ((len = in.read(buf)) > 0) {
268+
bytes.write(buf, 0, len);
269+
}
268270
}
269271
} catch (IOException e) {
270272
throw new RuntimeException("Failed to read resource '"+ref+"': "+e);

smile/src/test/java/com/fasterxml/jackson/dataformat/smile/fuzz/Fuzz32168BigDecimalTest.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ public class Fuzz32168BigDecimalTest extends BaseTestForSmile
1212
{
1313
private final ObjectMapper MAPPER = smileMapper();
1414

15-
// Payload:
1615
public void testInvalidBigDecimal() throws Exception
1716
{
1817
final byte[] input = new byte[] {
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package com.fasterxml.jackson.dataformat.smile.fuzz;
2+
3+
import com.fasterxml.jackson.core.*;
4+
import com.fasterxml.jackson.core.exc.StreamReadException;
5+
import com.fasterxml.jackson.databind.ObjectMapper;
6+
7+
import com.fasterxml.jackson.dataformat.smile.BaseTestForSmile;
8+
9+
public class Fuzz32527ShortUnicodeTest extends BaseTestForSmile
10+
{
11+
private final ObjectMapper MAPPER = smileMapper();
12+
13+
// [dataformats-binary#266]
14+
public void testInvalidShortUnicode() throws Exception
15+
{
16+
final byte[] input = new byte[] {
17+
0x3A, 0x29, 0x0A, 0x00, // smile signature
18+
(byte) 0xFA, // START_OBJECT
19+
(byte) 0xC8, // short-unicode-name: 10 bytes (0x8 + 2), 6 chars
20+
(byte) 0xC8, (byte) 0xC8,
21+
(byte) 0xC8, (byte) 0xC8, (byte) 0xC8, 0x00,
22+
0x00, (byte) 0xF3, (byte) 0xA0, (byte) 0x81,
23+
24+
(byte) 0x8A, // short-unicode-value: 12 bytes (0xA + 2)
25+
0x00, 0x01, 0x00,
26+
0x00, 0x00, 0x01, 0x01,
27+
0x00, 0x00, 0x04, (byte) 0xE5,
28+
0x04
29+
};
30+
try (JsonParser p = MAPPER.createParser(input)) {
31+
assertToken(JsonToken.START_OBJECT, p.nextToken());
32+
assertToken(JsonToken.FIELD_NAME, p.nextToken());
33+
assertEquals(6, p.currentName().length());
34+
assertToken(JsonToken.VALUE_STRING, p.nextToken());
35+
try {
36+
String text = p.getText();
37+
fail("Should have failed, instead decoded String of "+text.length()+" chars");
38+
} catch (StreamReadException e) {
39+
verifyException(e, "Truncated UTF-8 character in Short Unicode String");
40+
verifyException(e, "(12 bytes)");
41+
verifyException(e, "byte 0xE5 at offset #10 indicated 2 more bytes needed");
42+
}
43+
}
44+
}
45+
}

0 commit comments

Comments
 (0)