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 <thiago.macieira@intel.com>
This commit is contained in:
Thiago Macieira
2025-03-11 10:55:26 -07:00
parent 5bdc6ea3fe
commit e6924451a9
2 changed files with 57 additions and 15 deletions
+23 -15
View File
@@ -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);
}
/** @} */
+34
View File
@@ -22,8 +22,10 @@
**
****************************************************************************/
#define __STDC_WANT_IEC_60559_TYPES_EXT__
#include <QtTest>
#include "cbor.h"
#include "cborinternal_p.h"
#include "cborjson.h"
#include <locale.h>
@@ -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<QByteArray>("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<const quint8 *>(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)