Vulnerability: Variant_encodeJson in open62541 1.x before 1.0.4 has an out-of-bounds write for a large recursion depth.
Vulnerability ID: CVE-2020-36429
CWE: CWE-787
Severity score: 2.9
Project: https://github.com/open62541/open62541
Published: 2021-07-20
File: src/ua_types_encoding_json.c
Commit message: fix(json): Check max recursion depth in more places
Commit author: Julius Pfrommer

Vulnerable version (before fix):
 static WRITE_JSON_ELEMENT(Quote) {  WRITE_JSON_ELEMENT(ObjStart) {     /* increase depth, save: before first key-value no comma needed. */     ctx->depth++;     ctx->commaNeeded[ctx->depth] = false;     return writeChar(ctx, '{'); WRITE_JSON_ELEMENT(ObjEnd) {  WRITE_JSON_ELEMENT(ArrStart) {     /* increase depth, save: before first array entry no comma needed. */     ctx->commaNeeded[++ctx->depth] = false;     return writeChar(ctx, '['); }  addMultiArrayContentJSON(CtxJson *ctx, void* array, const UA_DataType *type,                          size_t *index, UA_UInt32 *arrayDimensions, size_t dimensionIndex,                           size_t dimensionSize) {     /* Check the recursion limit */     if(ctx->depth > UA_JSON_ENCODING_MAX_RECURSION)         return UA_STATUSCODE_BADENCODINGERROR;          /* Stop recursion: The inner Arrays are written */ ENCODE_JSON(DiagnosticInfo) { static status encodeJsonStructure(const void *src, const UA_DataType *type, CtxJson *ctx) {     /* Check the recursion limit */     if(ctx->depth > UA_JSON_ENCODING_MAX_RECURSION)         return UA_STATUSCODE_BADENCODINGERROR;     ctx->depth++;  decodeJsonStructure(void *dst, const UA_DataType *type, CtxJson *ctx,                     ParseCtx *parseCtx, UA_Boolean moveToken) {     (void) moveToken;     /* Check the recursion limit */     if(ctx->depth > UA_JSON_ENCODING_MAX_RECURSION)         return UA_STATUSCODE_BADENCODINGERROR;     ctx->depth++; 

Fixed version (after fix):
 static WRITE_JSON_ELEMENT(Quote) {  WRITE_JSON_ELEMENT(ObjStart) {     /* increase depth, save: before first key-value no comma needed. */     if(ctx->depth >= UA_JSON_ENCODING_MAX_RECURSION)         return UA_STATUSCODE_BADENCODINGERROR;     ctx->depth++;     ctx->commaNeeded[ctx->depth] = false;     return writeChar(ctx, '{'); WRITE_JSON_ELEMENT(ObjEnd) {  WRITE_JSON_ELEMENT(ArrStart) {     /* increase depth, save: before first array entry no comma needed. */     if(ctx->depth >= UA_JSON_ENCODING_MAX_RECURSION)         return UA_STATUSCODE_BADENCODINGERROR;     ctx->depth++;     ctx->commaNeeded[ctx->depth] = false;     return writeChar(ctx, '['); }  addMultiArrayContentJSON(CtxJson *ctx, void* array, const UA_DataType *type,                          size_t *index, UA_UInt32 *arrayDimensions, size_t dimensionIndex,                           size_t dimensionSize) {     /* Check the recursion limit */     if(ctx->depth >= UA_JSON_ENCODING_MAX_RECURSION)         return UA_STATUSCODE_BADENCODINGERROR;          /* Stop recursion: The inner Arrays are written */ ENCODE_JSON(DiagnosticInfo) { static status encodeJsonStructure(const void *src, const UA_DataType *type, CtxJson *ctx) {     /* Check the recursion limit */     if(ctx->depth >= UA_JSON_ENCODING_MAX_RECURSION)         return UA_STATUSCODE_BADENCODINGERROR;     ctx->depth++;  decodeJsonStructure(void *dst, const UA_DataType *type, CtxJson *ctx,                     ParseCtx *parseCtx, UA_Boolean moveToken) {     (void) moveToken;     /* Check the recursion limit */     if(ctx->depth >= UA_JSON_ENCODING_MAX_RECURSION)         return UA_STATUSCODE_BADENCODINGERROR;     ctx->depth++; 

Patch:
patch: @@ -108,6 +108,8 @@ static WRITE_JSON_ELEMENT(Quote) {
 
 WRITE_JSON_ELEMENT(ObjStart) {
     /* increase depth, save: before first key-value no comma needed. */
+    if(ctx->depth >= UA_JSON_ENCODING_MAX_RECURSION)
+        return UA_STATUSCODE_BADENCODINGERROR;
     ctx->depth++;
     ctx->commaNeeded[ctx->depth] = false;
     return writeChar(ctx, '{');
@@ -121,7 +123,10 @@ WRITE_JSON_ELEMENT(ObjEnd) {
 
 WRITE_JSON_ELEMENT(ArrStart) {
     /* increase depth, save: before first array entry no comma needed. */
-    ctx->commaNeeded[++ctx->depth] = false;
+    if(ctx->depth >= UA_JSON_ENCODING_MAX_RECURSION)
+        return UA_STATUSCODE_BADENCODINGERROR;
+    ctx->depth++;
+    ctx->commaNeeded[ctx->depth] = false;
     return writeChar(ctx, '[');
 }
 
@@ -1124,7 +1129,7 @@ addMultiArrayContentJSON(CtxJson *ctx, void* array, const UA_DataType *type,
                          size_t *index, UA_UInt32 *arrayDimensions, size_t dimensionIndex, 
                          size_t dimensionSize) {
     /* Check the recursion limit */
-    if(ctx->depth > UA_JSON_ENCODING_MAX_RECURSION)
+    if(ctx->depth >= UA_JSON_ENCODING_MAX_RECURSION)
         return UA_STATUSCODE_BADENCODINGERROR;
     
     /* Stop recursion: The inner Arrays are written */
@@ -1382,7 +1387,7 @@ ENCODE_JSON(DiagnosticInfo) {
 static status
 encodeJsonStructure(const void *src, const UA_DataType *type, CtxJson *ctx) {
     /* Check the recursion limit */
-    if(ctx->depth > UA_JSON_ENCODING_MAX_RECURSION)
+    if(ctx->depth >= UA_JSON_ENCODING_MAX_RECURSION)
         return UA_STATUSCODE_BADENCODINGERROR;
     ctx->depth++;
 
@@ -3153,7 +3158,7 @@ decodeJsonStructure(void *dst, const UA_DataType *type, CtxJson *ctx,
                     ParseCtx *parseCtx, UA_Boolean moveToken) {
     (void) moveToken;
     /* Check the recursion limit */
-    if(ctx->depth > UA_JSON_ENCODING_MAX_RECURSION)
+    if(ctx->depth >= UA_JSON_ENCODING_MAX_RECURSION)
         return UA_STATUSCODE_BADENCODINGERROR;
     ctx->depth++;
path: src/ua_types_encoding_json.c