Skip to content

Commit b7593a5

Browse files
committed
Merge branch '2.10' into 2.11
2 parents 8ab4268 + 3487af1 commit b7593a5

File tree

6 files changed

+232
-21
lines changed

6 files changed

+232
-21
lines changed

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

Lines changed: 207 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -877,37 +877,227 @@ 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
}
910-
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: We may actually need tag so decode it, but do not assign
941+
// (that'd override tag we already have)
942+
int tagValue = -1;
943+
if (type == 6) {
944+
tagValue = _decodeTag(ch & 0x1F);
945+
if ((_inputPtr >= _inputEnd) && !loadMore()) {
946+
_handleCBOREOF();
947+
return false;
948+
}
949+
ch = _inputBuffer[_inputPtr++];
950+
type = (ch >> 5) & 0x7;
951+
}
952+
953+
final int lowBits = ch & 0x1F;
954+
switch (type) {
955+
case 0: // positive int
956+
_numTypesValid = NR_INT;
957+
if (lowBits <= 23) {
958+
_numberInt = lowBits;
959+
} else {
960+
switch (lowBits - 24) {
961+
case 0:
962+
_numberInt = _decode8Bits();
963+
break;
964+
case 1:
965+
_numberInt = _decode16Bits();
966+
break;
967+
case 2:
968+
{
969+
int v = _decode32Bits();
970+
if (v >= 0) {
971+
_numberInt = v;
972+
} else {
973+
long l = (long) v;
974+
_numberLong = l & 0xFFFFFFFFL;
975+
_numTypesValid = NR_LONG;
976+
}
977+
}
978+
break;
979+
case 3:
980+
{
981+
long l = _decode64Bits();
982+
if (l >= 0L) {
983+
_numberLong = l;
984+
_numTypesValid = NR_LONG;
985+
} else {
986+
_numberBigInt = _bigPositive(l);
987+
_numTypesValid = NR_BIGINT;
988+
}
989+
}
990+
break;
991+
default:
992+
_invalidToken(ch);
993+
}
994+
}
995+
_currToken = JsonToken.VALUE_NUMBER_INT;
996+
return true;
997+
case 1: // negative int
998+
_numTypesValid = NR_INT;
999+
if (lowBits <= 23) {
1000+
_numberInt = -lowBits - 1;
1001+
} else {
1002+
switch (lowBits - 24) {
1003+
case 0:
1004+
_numberInt = -_decode8Bits() - 1;
1005+
break;
1006+
case 1:
1007+
_numberInt = -_decode16Bits() - 1;
1008+
break;
1009+
case 2:
1010+
// 15-Oct-2016, as per [dataformats-binary#30], we got an edge case here
1011+
{
1012+
int v = _decode32Bits();
1013+
if (v < 0) {
1014+
long unsignedBase = (long) v & 0xFFFFFFFFL;
1015+
_numberLong = -unsignedBase - 1L;
1016+
_numTypesValid = NR_LONG;
1017+
} else {
1018+
_numberInt = -v - 1;
1019+
}
1020+
}
1021+
break;
1022+
case 3:
1023+
// 15-Oct-2016, as per [dataformats-binary#30], we got an edge case here
1024+
{
1025+
long l = _decode64Bits();
1026+
if (l >= 0L) {
1027+
_numberLong = -l - 1L;
1028+
_numTypesValid = NR_LONG;
1029+
} else {
1030+
_numberBigInt = _bigNegative(l);
1031+
_numTypesValid = NR_BIGINT;
1032+
}
1033+
}
1034+
break;
1035+
default:
1036+
_invalidToken(ch);
1037+
}
1038+
}
1039+
_currToken = JsonToken.VALUE_NUMBER_INT;
1040+
return true;
1041+
1042+
case 2: // byte[]
1043+
// ... but we only really care about very specific case of `BigInteger`
1044+
if (tagValue < 0) {
1045+
break;
1046+
}
1047+
_typeByte = ch;
1048+
_tokenIncomplete = true;
1049+
_currToken = _handleTaggedBinary(tagValue);
1050+
return (_currToken == JsonToken.VALUE_NUMBER_INT);
1051+
1052+
case 6: // another tag; not allowed
1053+
_reportError("Multiple tags not allowed per value (first tag: "+tagValue+")");
1054+
}
1055+
1056+
// Important! Need to push back the last byte read (but not consumed)
1057+
--_inputPtr;
1058+
// and now it is safe to decode next token, too
1059+
nextToken();
1060+
return false;
1061+
}
1062+
1063+
protected final boolean _checkNextIsEndArray() throws IOException
1064+
{
1065+
// We know we are in array, with length prefix, and this is where we should be:
1066+
if (!_parsingContext.expectMoreValues()) {
1067+
_tagValue = -1;
1068+
_parsingContext = _parsingContext.getParent();
1069+
_currToken = JsonToken.END_ARRAY;
1070+
return true;
1071+
}
1072+
1073+
// But while we otherwise could bail out we should check what follows for better
1074+
// error reporting... yet we ALSO must avoid direct call to `nextToken()` to avoid
1075+
// [dataformats-binary#185]
1076+
int ch = _inputBuffer[_inputPtr++];
1077+
int type = (ch >> 5) & 0x7;
1078+
1079+
// No use for tag but removing it is necessary
1080+
int tagValue = -1;
1081+
if (type == 6) {
1082+
tagValue = _decodeTag(ch & 0x1F);
1083+
if ((_inputPtr >= _inputEnd) && !loadMore()) {
1084+
_handleCBOREOF();
1085+
return false;
1086+
}
1087+
ch = _inputBuffer[_inputPtr++];
1088+
type = (ch >> 5) & 0x7;
1089+
// including but not limited to nested tags (which we do not allow)
1090+
if (type == 6) {
1091+
_reportError("Multiple tags not allowed per value (first tag: "+tagValue+")");
1092+
}
1093+
}
1094+
// and that's what we need to do for safety; now can drop to generic handling:
1095+
1096+
// Important! Need to push back the last byte read (but not consumed)
1097+
--_inputPtr;
1098+
return nextToken() == JsonToken.END_ARRAY; // should never match
1099+
}
1100+
9111101
// base impl is fine:
9121102
//public String getCurrentName() throws IOException
9131103

@@ -1467,7 +1657,7 @@ public byte[] getBinaryValue(Base64Variant b64variant) throws IOException
14671657
}
14681658
if (_currToken != JsonToken.VALUE_EMBEDDED_OBJECT ) {
14691659
// TODO, maybe: support base64 for text?
1470-
_reportError("Current token ("+getCurrentToken()+") not VALUE_EMBEDDED_OBJECT, can not access as binary");
1660+
_reportError("Current token ("+currentToken()+") not VALUE_EMBEDDED_OBJECT, can not access as binary");
14711661
}
14721662
return _binaryValue;
14731663
}
@@ -1489,7 +1679,7 @@ public int readBinaryValue(Base64Variant b64variant, OutputStream out) throws IO
14891679
{
14901680
if (_currToken != JsonToken.VALUE_EMBEDDED_OBJECT ) {
14911681
// Todo, maybe: support base64 for text?
1492-
_reportError("Current token ("+getCurrentToken()+") not VALUE_EMBEDDED_OBJECT, can not access as binary");
1682+
_reportError("Current token ("+currentToken()+") not VALUE_EMBEDDED_OBJECT, can not access as binary");
14931683
}
14941684
if (!_tokenIncomplete) { // someone already decoded or read
14951685
if (_binaryValue == null) { // if this method called twice in a row
@@ -1724,7 +1914,7 @@ protected void _checkNumericValue(int expType) throws IOException
17241914
if (_currToken == JsonToken.VALUE_NUMBER_INT || _currToken == JsonToken.VALUE_NUMBER_FLOAT) {
17251915
return;
17261916
}
1727-
_reportError("Current token ("+getCurrentToken()+") not numeric, can not use numeric value accessors");
1917+
_reportError("Current token ("+currentToken()+") not numeric, can not use numeric value accessors");
17281918
}
17291919

17301920
protected void convertNumberToInt() throws IOException
@@ -2770,11 +2960,11 @@ private final int _decodeExplicitLength(int lowBits) throws IOException
27702960
case 3:
27712961
long l = _decode64Bits();
27722962
if (l < 0 || l > MAX_INT_L) {
2773-
throw _constructError("Illegal length for "+getCurrentToken()+": "+l);
2963+
throw _constructError("Illegal length for "+currentToken()+": "+l);
27742964
}
27752965
return (int) l;
27762966
}
2777-
throw _constructError("Invalid length for "+getCurrentToken()+": 0x"+Integer.toHexString(lowBits));
2967+
throw _constructError("Invalid length for "+currentToken()+": 0x"+Integer.toHexString(lowBits));
27782968
}
27792969

27802970
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
@@ -12,6 +12,11 @@ Project: jackson-datatypes-binaryModules:
1212

1313
-
1414

15+
2.10.1 (not yet released)
16+
17+
#185: Internal parsing of tagged arrays can lead to stack overflow
18+
(reported by Paul A)
19+
1520
2.10.0 (26-Sep-2019)
1621

1722
#139: (cbor) Incorrect decimal fraction representation

0 commit comments

Comments
 (0)