From 5945ac8ed34ef0f92998f5768a8efe986e1e890a Mon Sep 17 00:00:00 2001 From: Thiago Macieira Date: Tue, 4 Feb 2025 16:40:45 -0800 Subject: [PATCH] CBOR-to-JSON: Limit how deep we process nested containers 1024 levels will probably be good enough for everyone, like cborparser.c. For those for whom it isn't, they can set the limit during the build. We already had this for the plain parser, so TinyCBOR wouldn't cause a stack overflow in case of a malformed stream (intentionally or not) when simply parsing and advancing over the stream. This same protection wasn't applied to the content converting from CBOR to JSON. Signed-off-by: Thiago Macieira --- src/cbortojson.c | 38 ++++++++++++++++++++++--------------- tests/tojson/tst_tojson.cpp | 34 +++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 15 deletions(-) diff --git a/src/cbortojson.c b/src/cbortojson.c index 890769c7..1f74e32f 100644 --- a/src/cbortojson.c +++ b/src/cbortojson.c @@ -167,7 +167,8 @@ typedef struct ConversionStatus { int flags; } ConversionStatus; -static CborError value_to_json(FILE *out, CborValue *it, int flags, CborType type, ConversionStatus *status); +static CborError value_to_json(FILE *out, CborValue *it, int flags, CborType type, + int nestingLevel, ConversionStatus *status); static CborError dump_bytestring_base16(char **result, CborValue *it) { @@ -328,11 +329,13 @@ static CborError add_value_metadata(FILE *out, CborType type, const ConversionSt return CborNoError; } -static CborError find_tagged_type(CborValue *it, CborTag *tag, CborType *type) +static CborError find_tagged_type(CborValue *it, CborTag *tag, CborType *type, int nestingLevel) { CborError err = CborNoError; *type = cbor_value_get_type(it); while (*type == CborTagType) { + if (nestingLevel-- == 0) + return CborErrorNestingTooDeep; cbor_value_get_tag(it, tag); /* can't fail */ err = cbor_value_advance_fixed(it); if (err) @@ -343,7 +346,7 @@ static CborError find_tagged_type(CborValue *it, CborTag *tag, CborType *type) return err; } -static CborError tagged_value_to_json(FILE *out, CborValue *it, int flags, ConversionStatus *status) +static CborError tagged_value_to_json(FILE *out, CborValue *it, int flags, int nestingLevel, ConversionStatus *status) { CborTag tag; CborError err; @@ -358,7 +361,7 @@ static CborError tagged_value_to_json(FILE *out, CborValue *it, int flags, Conve return CborErrorIO; CborType type = cbor_value_get_type(it); - err = value_to_json(out, it, flags, type, status); + err = value_to_json(out, it, flags, type, nestingLevel, status); if (err) return err; if (flags & CborConvertAddMetadata && status->flags) { @@ -374,7 +377,7 @@ static CborError tagged_value_to_json(FILE *out, CborValue *it, int flags, Conve } CborType type; - err = find_tagged_type(it, &status->lastTag, &type); + err = find_tagged_type(it, &status->lastTag, &type, nestingLevel); if (err) return err; tag = status->lastTag; @@ -402,7 +405,7 @@ static CborError tagged_value_to_json(FILE *out, CborValue *it, int flags, Conve } /* no special handling */ - err = value_to_json(out, it, flags, type, status); + err = value_to_json(out, it, flags, type, nestingLevel, status); status->flags |= TypeWasTagged | type; return err; } @@ -429,7 +432,7 @@ static CborError stringify_map_key(char **key, CborValue *it, int flags, CborTyp #endif } -static CborError array_to_json(FILE *out, CborValue *it, int flags, ConversionStatus *status) +static CborError array_to_json(FILE *out, CborValue *it, int flags, int nestingLevel, ConversionStatus *status) { const char *comma = ""; while (!cbor_value_at_end(it)) { @@ -437,14 +440,14 @@ static CborError array_to_json(FILE *out, CborValue *it, int flags, ConversionSt return CborErrorIO; comma = ","; - CborError err = value_to_json(out, it, flags, cbor_value_get_type(it), status); + CborError err = value_to_json(out, it, flags, cbor_value_get_type(it), nestingLevel, status); if (err) return err; } return CborNoError; } -static CborError map_to_json(FILE *out, CborValue *it, int flags, ConversionStatus *status) +static CborError map_to_json(FILE *out, CborValue *it, int flags, int nestingLevel, ConversionStatus *status) { const char *comma = ""; CborError err; @@ -474,7 +477,7 @@ static CborError map_to_json(FILE *out, CborValue *it, int flags, ConversionStat /* then, print the value */ CborType valueType = cbor_value_get_type(it); - err = value_to_json(out, it, flags, valueType, status); + err = value_to_json(out, it, flags, valueType, nestingLevel, status); /* finally, print any metadata we may have */ if (flags & CborConvertAddMetadata) { @@ -497,11 +500,15 @@ static CborError map_to_json(FILE *out, CborValue *it, int flags, ConversionStat return CborNoError; } -static CborError value_to_json(FILE *out, CborValue *it, int flags, CborType type, ConversionStatus *status) +static CborError value_to_json(FILE *out, CborValue *it, int flags, CborType type, + int nestingLevel, ConversionStatus *status) { CborError err; status->flags = 0; + if (nestingLevel == 0) + return CborErrorNestingTooDeep; + switch (type) { case CborArrayType: case CborMapType: { @@ -516,8 +523,8 @@ static CborError value_to_json(FILE *out, CborValue *it, int flags, CborType typ return CborErrorIO; err = (type == CborArrayType) ? - array_to_json(out, &recursed, flags, status) : - map_to_json(out, &recursed, flags, status); + array_to_json(out, &recursed, flags, nestingLevel - 1, status) : + map_to_json(out, &recursed, flags, nestingLevel - 1, status); if (err) { copy_current_position(it, &recursed); return err; /* parse error */ @@ -574,7 +581,7 @@ static CborError value_to_json(FILE *out, CborValue *it, int flags, CborType typ } case CborTagType: - return tagged_value_to_json(out, it, flags, status); + return tagged_value_to_json(out, it, flags, nestingLevel - 1, status); case CborSimpleType: { uint8_t simple_type; @@ -704,7 +711,8 @@ static CborError value_to_json(FILE *out, CborValue *it, int flags, CborType typ CborError cbor_value_to_json_advance(FILE *out, CborValue *value, int flags) { ConversionStatus status; - return value_to_json(out, value, flags, cbor_value_get_type(value), &status); + return value_to_json(out, value, flags, cbor_value_get_type(value), CBOR_PARSER_MAX_RECURSIONS, + &status); } /** @} */ diff --git a/tests/tojson/tst_tojson.cpp b/tests/tojson/tst_tojson.cpp index 89b5d129..27603ea6 100644 --- a/tests/tojson/tst_tojson.cpp +++ b/tests/tojson/tst_tojson.cpp @@ -22,8 +22,10 @@ ** ****************************************************************************/ +#define __STDC_WANT_IEC_60559_TYPES_EXT__ #include #include "cbor.h" +#include "cborinternal_p.h" #include "cborjson.h" #include @@ -73,6 +75,9 @@ private slots: void metaDataAndTagsToObjects(); void metaDataForKeys_data(); void metaDataForKeys(); + + void recursionLimit_data(); + void recursionLimit(); }; #include "tst_tojson.moc" @@ -718,4 +723,33 @@ void tst_ToJson::metaDataForKeys() CborConvertAddMetadata | CborConvertStringifyMapKeys); } +void tst_ToJson::recursionLimit_data() +{ + static const int recursions = CBOR_PARSER_MAX_RECURSIONS + 2; + QTest::addColumn("data"); + + QTest::newRow("arrays") << QByteArray(recursions, '\x81'); + QTest::newRow("tags") << QByteArray(recursions, '\xc1'); + + QByteArray mapData; + mapData.reserve(2 * recursions); + for (int i = 0; i < recursions; ++i) + mapData.append("\xa1\x60", 2); + QTest::newRow("maps") << mapData; +} + +void tst_ToJson::recursionLimit() +{ + QFETCH(QByteArray, data); + CborParser parser; + CborValue first; + CborError err = cbor_parser_init(reinterpret_cast(data.constData()), data.length(), 0, &parser, &first); + QVERIFY2(!err, QByteArray("Got error \"") + cbor_error_string(err) + "\""); + + QString parsed; + err = parseOne(&first, &parsed, 0); + + QCOMPARE(err, CborErrorNestingTooDeep); +} + QTEST_MAIN(tst_ToJson)