Skip to content

Commit eafae59

Browse files
committed
fix #185 (possible stackoverflow decoding crafted nested tagged arrays)
1 parent ccd0fc4 commit eafae59

File tree

6 files changed

+200
-20
lines changed

6 files changed

+200
-20
lines changed

cbor/src/main/java/com/fasterxml/jackson/dataformat/cbor/CBORParser.java

Lines changed: 175 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -877,36 +877,195 @@ protected JsonToken _handleTaggedArray(int tag, int len) throws IOException
877877
_reportError("Unexpected array size ("+len+") for tagged 'bigfloat' value; should have exactly 2 number elements");
878878
}
879879
// and then use recursion to get values
880-
JsonToken t = nextToken();
881880
// First: exponent, which MUST be a simple integer value
882-
if (t != JsonToken.VALUE_NUMBER_INT) {
883-
_reportError("Unexpected token ("+t+") as the first part of 'bigfloat' value: should get VALUE_NUMBER_INT");
881+
if (!_checkNextIsIntInArray("bigfloat")) {
882+
_reportError("Unexpected token ("+currentToken()+") as the first part of 'bigfloat' value: should get VALUE_NUMBER_INT");
884883
}
885884
// 27-Nov-2019, tatu: As per [dataformats-binary#139] need to change sign here
886885
int exp = -getIntValue();
887886

888-
t = nextToken();
889887
// Should get an integer value; int/long/BigInteger
890-
if (t != JsonToken.VALUE_NUMBER_INT) {
891-
_reportError("Unexpected token ("+t+") as the second part of 'bigfloat' value: should get VALUE_NUMBER_INT");
888+
if (!_checkNextIsIntInArray("bigfloat")) {
889+
_reportError("Unexpected token ("+currentToken()+") as the second part of 'bigfloat' value: should get VALUE_NUMBER_INT");
892890
}
893-
894-
BigDecimal dec;
895891

892+
// important: check number type here
893+
BigDecimal dec;
896894
NumberType numberType = getNumberType();
897895
if (numberType == NumberType.BIG_INTEGER) {
898896
dec = new BigDecimal(getBigIntegerValue(), exp);
899897
} else {
900898
dec = BigDecimal.valueOf(getLongValue(), exp);
901899
}
902-
t = nextToken();
903-
if (t != JsonToken.END_ARRAY) {
904-
_reportError("Unexpected token ("+t+") after 2 elements of 'bigfloat' value");
900+
901+
// but verify closing END_ARRAY here, as this will now override current token
902+
if (!_checkNextIsEndArray()) {
903+
_reportError("Unexpected token ("+currentToken()+") after 2 elements of 'bigfloat' value");
905904
}
905+
906+
// which needs to be reset here
906907
_numberBigDecimal = dec;
907908
_numTypesValid = NR_BIGDECIMAL;
908909
return (_currToken = JsonToken.VALUE_NUMBER_FLOAT);
909910
}
911+
912+
/**
913+
* Heavily simplified method that does a subset of what {@code nextTokendoes to basically
914+
* only (1) determine that we are getting {@code JsonToken.VALUE_NUMBER_INT} (if not,
915+
* return with no processing) and (2) if so, prepare state so that number accessor
916+
* method will work).
917+
*<p>
918+
* Note that in particular this method DOES NOT reset state that {@code nextToken()} would do,
919+
* but will change current token type to allow access.
920+
*/
921+
protected final boolean _checkNextIsIntInArray(final String typeDesc) throws IOException
922+
{
923+
// We know we are in array, with length prefix so:
924+
if (!_parsingContext.expectMoreValues()) {
925+
_tagValue = -1;
926+
_parsingContext = _parsingContext.getParent();
927+
_currToken = JsonToken.END_ARRAY;
928+
return false;
929+
}
930+
931+
if (_inputPtr >= _inputEnd) {
932+
if (!loadMore()) {
933+
_handleCBOREOF();
934+
return false;
935+
}
936+
}
937+
int ch = _inputBuffer[_inputPtr++];
938+
int type = (ch >> 5) & 0x7;
939+
940+
// 01-Nov-2019, tatu: Although we don't use tag for anything, might as well decode it
941+
// for funsies and just ignore.
942+
int tagValue = -1;
943+
if (type == 6) {
944+
tagValue = _decodeTag(ch & 0x1F);
945+
if (_inputPtr >= _inputEnd) {
946+
if (!loadMore()) {
947+
_handleCBOREOF();
948+
return false;
949+
}
950+
}
951+
ch = _inputBuffer[_inputPtr++];
952+
type = (ch >> 5) & 0x7;
953+
}
954+
955+
final int lowBits = ch & 0x1F;
956+
switch (type) {
957+
case 0: // positive int
958+
_numTypesValid = NR_INT;
959+
if (lowBits <= 23) {
960+
_numberInt = lowBits;
961+
} else {
962+
switch (lowBits - 24) {
963+
case 0:
964+
_numberInt = _decode8Bits();
965+
break;
966+
case 1:
967+
_numberInt = _decode16Bits();
968+
break;
969+
case 2:
970+
{
971+
int v = _decode32Bits();
972+
if (v >= 0) {
973+
_numberInt = v;
974+
} else {
975+
long l = (long) v;
976+
_numberLong = l & 0xFFFFFFFFL;
977+
_numTypesValid = NR_LONG;
978+
}
979+
}
980+
break;
981+
case 3:
982+
{
983+
long l = _decode64Bits();
984+
if (l >= 0L) {
985+
_numberLong = l;
986+
_numTypesValid = NR_LONG;
987+
} else {
988+
_numberBigInt = _bigPositive(l);
989+
_numTypesValid = NR_BIGINT;
990+
}
991+
}
992+
break;
993+
default:
994+
_invalidToken(ch);
995+
}
996+
}
997+
_currToken = JsonToken.VALUE_NUMBER_INT;
998+
return true;
999+
case 1: // negative int
1000+
_numTypesValid = NR_INT;
1001+
if (lowBits <= 23) {
1002+
_numberInt = -lowBits - 1;
1003+
} else {
1004+
switch (lowBits - 24) {
1005+
case 0:
1006+
_numberInt = -_decode8Bits() - 1;
1007+
break;
1008+
case 1:
1009+
_numberInt = -_decode16Bits() - 1;
1010+
break;
1011+
case 2:
1012+
// 15-Oct-2016, as per [dataformats-binary#30], we got an edge case here
1013+
{
1014+
int v = _decode32Bits();
1015+
if (v < 0) {
1016+
long unsignedBase = (long) v & 0xFFFFFFFFL;
1017+
_numberLong = -unsignedBase - 1L;
1018+
_numTypesValid = NR_LONG;
1019+
} else {
1020+
_numberInt = -v - 1;
1021+
}
1022+
}
1023+
break;
1024+
case 3:
1025+
// 15-Oct-2016, as per [dataformats-binary#30], we got an edge case here
1026+
{
1027+
long l = _decode64Bits();
1028+
if (l >= 0L) {
1029+
_numberLong = -l - 1L;
1030+
_numTypesValid = NR_LONG;
1031+
} else {
1032+
_numberBigInt = _bigNegative(l);
1033+
_numTypesValid = NR_BIGINT;
1034+
}
1035+
}
1036+
break;
1037+
default:
1038+
_invalidToken(ch);
1039+
}
1040+
}
1041+
_currToken = JsonToken.VALUE_NUMBER_INT;
1042+
return true;
1043+
1044+
case 2: // byte[]
1045+
// ... but we only really care about very specific case of `BigInteger`
1046+
if (tagValue < 0) {
1047+
break;
1048+
}
1049+
_typeByte = ch;
1050+
_tokenIncomplete = true;
1051+
_currToken = _handleTaggedBinary(tagValue);
1052+
return (_currToken == JsonToken.VALUE_NUMBER_INT);
1053+
1054+
case 6: // another tag; not allowed
1055+
_reportError("Multiple tags not allowed per value (first tag: "+tagValue+")");
1056+
}
1057+
1058+
// Important! Need to push back the last byte read (but not consumed)
1059+
--_inputPtr;
1060+
// and now it is safe to decode next token, too
1061+
nextToken();
1062+
return false;
1063+
}
1064+
1065+
protected final boolean _checkNextIsEndArray() throws IOException
1066+
{
1067+
return nextToken() == JsonToken.END_ARRAY;
1068+
}
9101069

9111070
// base impl is fine:
9121071
//public String getCurrentName() throws IOException
@@ -1467,7 +1626,7 @@ public byte[] getBinaryValue(Base64Variant b64variant) throws IOException
14671626
}
14681627
if (_currToken != JsonToken.VALUE_EMBEDDED_OBJECT ) {
14691628
// TODO, maybe: support base64 for text?
1470-
_reportError("Current token ("+getCurrentToken()+") not VALUE_EMBEDDED_OBJECT, can not access as binary");
1629+
_reportError("Current token ("+currentToken()+") not VALUE_EMBEDDED_OBJECT, can not access as binary");
14711630
}
14721631
return _binaryValue;
14731632
}
@@ -1489,7 +1648,7 @@ public int readBinaryValue(Base64Variant b64variant, OutputStream out) throws IO
14891648
{
14901649
if (_currToken != JsonToken.VALUE_EMBEDDED_OBJECT ) {
14911650
// Todo, maybe: support base64 for text?
1492-
_reportError("Current token ("+getCurrentToken()+") not VALUE_EMBEDDED_OBJECT, can not access as binary");
1651+
_reportError("Current token ("+currentToken()+") not VALUE_EMBEDDED_OBJECT, can not access as binary");
14931652
}
14941653
if (!_tokenIncomplete) { // someone already decoded or read
14951654
if (_binaryValue == null) { // if this method called twice in a row
@@ -1724,7 +1883,7 @@ protected void _checkNumericValue(int expType) throws IOException
17241883
if (_currToken == JsonToken.VALUE_NUMBER_INT || _currToken == JsonToken.VALUE_NUMBER_FLOAT) {
17251884
return;
17261885
}
1727-
_reportError("Current token ("+getCurrentToken()+") not numeric, can not use numeric value accessors");
1886+
_reportError("Current token ("+currentToken()+") not numeric, can not use numeric value accessors");
17281887
}
17291888

17301889
protected void convertNumberToInt() throws IOException
@@ -2770,11 +2929,11 @@ private final int _decodeExplicitLength(int lowBits) throws IOException
27702929
case 3:
27712930
long l = _decode64Bits();
27722931
if (l < 0 || l > MAX_INT_L) {
2773-
throw _constructError("Illegal length for "+getCurrentToken()+": "+l);
2932+
throw _constructError("Illegal length for "+currentToken()+": "+l);
27742933
}
27752934
return (int) l;
27762935
}
2777-
throw _constructError("Invalid length for "+getCurrentToken()+": 0x"+Integer.toHexString(lowBits));
2936+
throw _constructError("Invalid length for "+currentToken()+": 0x"+Integer.toHexString(lowBits));
27782937
}
27792938

27802939
private int _decodeChunkLength(int expType) throws IOException

cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/parse/BigNumbersTest.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
// tests for [cbor#17]
1313
public class BigNumbersTest extends CBORTestBase
1414
{
15-
public void testBigDecimal() throws Exception
15+
public void testBigDecimalShort() throws Exception
1616
{
1717
_testBigDecimal(BigDecimal.ONE);
1818
_testBigDecimal(BigDecimal.ZERO);
@@ -31,9 +31,12 @@ public void testBigDecimal() throws Exception
3131
BigDecimal bd = new BigDecimal("12345.667899024");
3232
_testBigDecimal(bd);
3333
_testBigDecimal(bd.negate());
34+
}
3435

36+
public void testBigDecimalLonger() throws Exception
37+
{
3538
// ensure mantissa is beyond long; more than 22 digits or so
36-
bd = new BigDecimal("1234567890.12345678901234567890");
39+
BigDecimal bd = new BigDecimal("1234567890.12345678901234567890");
3740
_testBigDecimal(bd);
3841
_testBigDecimal(bd.negate());
3942
}

cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/parse/ParserNumbersTest.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,7 +343,9 @@ public void testBigDecimalType() throws IOException {
343343
assertEquals(NR.intValue(), parser.getIntValue());
344344
assertNull(parser.nextToken());
345345
}
346+
}
346347

348+
public void testBigDecimalType2() throws IOException {
347349
// Almost good. But [dataformats#139] to consider too, see
348350
// [https://tools.ietf.org/html/rfc7049#section-2.4.2]
349351
final byte[] spec = new byte[] {

cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/failing/TagParsing185Test.java renamed to cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/parse/TagParsing185Test.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package com.fasterxml.jackson.dataformat.cbor.failing;
1+
package com.fasterxml.jackson.dataformat.cbor.parse;
22

33
import com.fasterxml.jackson.core.*;
44

@@ -24,7 +24,14 @@ private void _testRecursiveTags(int levels) throws Exception
2424
}
2525

2626
JsonParser p = CBOR_F.createParser(data);
27+
JsonToken t;
2728

28-
assertToken(JsonToken.START_ARRAY, p.nextToken());
29+
try {
30+
t = p.nextToken();
31+
fail("Should not pass, got token: "+t);
32+
} catch (JsonParseException e) {
33+
verifyException(e, "Unexpected token");
34+
verifyException(e, "first part of 'bigfloat' value");
35+
}
2936
}
3037
}

release-notes/CREDITS-2.x

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,3 +108,7 @@ Marcos Passos (marcospassos@github)
108108
John (iziamos@github)
109109
* Reported, suggested fix for #178: Fix issue wit input offsets when parsing CBOR from `InputStream`
110110
(2.10.0)
111+
112+
Paul Adolph (padolph@github)
113+
* Reported #185: Internal parsing of tagged arrays can lead to stack overflow
114+
(2.10.1)

release-notes/VERSION-2.x

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@ Project: jackson-datatypes-binaryModules:
88
=== Releases ===
99
------------------------------------------------------------------------
1010

11+
2.10.1 (not yet released)
12+
13+
#185: Internal parsing of tagged arrays can lead to stack overflow
14+
(reported by Paul A)
15+
1116
2.10.0 (26-Sep-2019)
1217

1318
#139: (cbor) Incorrect decimal fraction representation

0 commit comments

Comments
 (0)