-
Notifications
You must be signed in to change notification settings - Fork 74
Fix JSON validation issues concerning missing commas, escaped control characters, and hex escaped 0 value. #179
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
4c4f597
10fd66c
eebe8bc
e5c12ff
684af6d
92ab41f
94ed2de
8c5a830
ef1f39c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,11 +1,11 @@ | ||
| diff --git a/source/core_json.c b/source/core_json.c | ||
| index d8694f0..761c44b 100644 | ||
| index a01c393..ad48f28 100644 | ||
| --- a/source/core_json.c | ||
| +++ b/source/core_json.c | ||
| @@ -63,6 +63,21 @@ typedef union | ||
| #define isCurlyOpen_( x ) ( ( x ) == '{' ) | ||
| #define isCurlyClose_( x ) ( ( x ) == '}' ) | ||
|
|
||
| +/** | ||
| + * Renaming all loop-contract clauses from CBMC for readability. | ||
| + * For more information about loop contracts in CBMC, see | ||
|
|
@@ -26,7 +26,7 @@ index d8694f0..761c44b 100644 | |
| * | ||
| @@ -79,6 +94,9 @@ static void skipSpace( const char * buf, | ||
| coreJSON_ASSERT( ( buf != NULL ) && ( start != NULL ) && ( max > 0U ) ); | ||
|
|
||
| for( i = *start; i < max; i++ ) | ||
| + assigns( i ) | ||
| + loopInvariant( *start <= i && i <= max ) | ||
|
|
@@ -36,7 +36,7 @@ index d8694f0..761c44b 100644 | |
| { | ||
| @@ -103,6 +121,13 @@ static size_t countHighBits( uint8_t c ) | ||
| size_t i = 0; | ||
|
|
||
| while( ( n & 0x80U ) != 0U ) | ||
| + assigns( i, n ) | ||
| + loopInvariant ( | ||
|
|
@@ -61,9 +61,9 @@ index d8694f0..761c44b 100644 | |
| + decreases( j ) | ||
| { | ||
| i++; | ||
|
|
||
| @@ -346,6 +378,12 @@ static bool skipOneHexEscape( const char * buf, | ||
| if( ( end < max ) && ( buf[ i ] == '\\' ) && ( buf[ i + 1U ] == 'u' ) ) | ||
| @@ -343,6 +375,12 @@ static bool skipOneHexEscape( const char * buf, | ||
| if( ( end > i ) && ( end < max ) && ( buf[ i ] == '\\' ) && ( buf[ i + 1U ] == 'u' ) ) | ||
| { | ||
| for( i += 2U; i < end; i++ ) | ||
| + assigns( value, i ) | ||
|
|
@@ -74,50 +74,50 @@ index d8694f0..761c44b 100644 | |
| + decreases( end - i ) | ||
| { | ||
| uint8_t n = hexToInt( buf[ i ] ); | ||
|
|
||
| @@ -523,6 +561,9 @@ static bool skipString( const char * buf, | ||
| @@ -505,6 +543,9 @@ static bool skipString( const char * buf, | ||
| i++; | ||
|
|
||
| while( i < max ) | ||
| + assigns( i ) | ||
| + loopInvariant( *start + 1U <= i && i <= max ) | ||
| + decreases( max - i ) | ||
| { | ||
| if( buf[ i ] == '"' ) | ||
| { | ||
| @@ -581,6 +622,9 @@ static bool strnEq( const char * a, | ||
| @@ -563,6 +604,9 @@ static bool strnEq( const char * a, | ||
| coreJSON_ASSERT( ( a != NULL ) && ( b != NULL ) ); | ||
|
|
||
| for( i = 0; i < n; i++ ) | ||
| + assigns( i ) | ||
| + loopInvariant( i <= n ) | ||
| + decreases( n - i ) | ||
| { | ||
| if( a[ i ] != b[ i ] ) | ||
| { | ||
| @@ -696,6 +740,9 @@ static bool skipDigits( const char * buf, | ||
| @@ -678,6 +722,9 @@ static bool skipDigits( const char * buf, | ||
| saveStart = *start; | ||
|
|
||
| for( i = *start; i < max; i++ ) | ||
| + assigns( value, i ) | ||
| + loopInvariant( *start <= i && i <= max ) | ||
| + decreases( max - i ) | ||
| { | ||
| if( !isdigit_( buf[ i ] ) ) | ||
| { | ||
| @@ -945,6 +992,9 @@ static void skipArrayScalars( const char * buf, | ||
| @@ -928,6 +975,9 @@ static bool skipArrayScalars( const char * buf, | ||
| i = *start; | ||
|
|
||
| while( i < max ) | ||
| + assigns( i ) | ||
| + loopInvariant( *start <= i && i <= max ) | ||
| + decreases( max - i ) | ||
| { | ||
| if( skipAnyScalar( buf, &i, max ) != true ) | ||
| { | ||
| @@ -991,6 +1041,13 @@ static bool skipObjectScalars( const char * buf, | ||
| @@ -982,6 +1032,13 @@ static bool skipObjectScalars( const char * buf, | ||
| i = *start; | ||
|
|
||
| while( i < max ) | ||
| + assigns( i, *start, comma ) | ||
| + loopInvariant( | ||
|
|
@@ -129,11 +129,11 @@ index d8694f0..761c44b 100644 | |
| { | ||
| if( skipString( buf, &i, max ) != true ) | ||
| { | ||
| @@ -1118,6 +1175,14 @@ static JSONStatus_t skipCollection( const char * buf, | ||
| @@ -1109,6 +1166,14 @@ static JSONStatus_t skipCollection( const char * buf, | ||
| i = *start; | ||
|
|
||
| while( i < max ) | ||
| + assigns( i, depth, c, __CPROVER_object_whole( stack ), ret ) | ||
| + assigns( i, depth, c, ret, __CPROVER_object_whole( stack ) ) | ||
| + loopInvariant( | ||
| + -1 <= depth && depth <= JSON_MAX_DEPTH | ||
| + && *start <= i && i <= max | ||
|
|
@@ -144,27 +144,35 @@ index d8694f0..761c44b 100644 | |
| { | ||
| c = buf[ i ]; | ||
| i++; | ||
| @@ -1407,6 +1472,9 @@ static bool objectSearch( const char * buf, | ||
| @@ -1144,6 +1209,7 @@ static JSONStatus_t skipCollection( const char * buf, | ||
|
|
||
| if( skipSpaceAndComma( buf, &i, max ) == true ) | ||
| { | ||
| + __CPROVER_assume( isOpenBracket_(stack[depth])); | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have verified by code inspection that this assumption should hold for the loop - I would ask that reviewers verify this as well - see comment about redundant isOpenBracket_ check. |
||
| if( skipScalars( buf, &i, max, stack[ depth ] ) != true ) | ||
| { | ||
| ret = JSONIllegalDocument; | ||
| @@ -1406,6 +1472,9 @@ static bool objectSearch( const char * buf, | ||
| skipSpace( buf, &i, max ); | ||
|
|
||
| while( i < max ) | ||
| + assigns( i, key, keyLength, value, valueLength ) | ||
| + loopInvariant( __CPROVER_loop_entry( i ) <= i && i <= max ) | ||
| + decreases( max - i ) | ||
| { | ||
| if( nextKeyValuePair( buf, &i, max, &key, &keyLength, | ||
| &value, &valueLength ) != true ) | ||
| @@ -1474,6 +1542,9 @@ static bool arraySearch( const char * buf, | ||
| @@ -1473,6 +1542,9 @@ static bool arraySearch( const char * buf, | ||
| skipSpace( buf, &i, max ); | ||
|
|
||
| while( i < max ) | ||
| + assigns( i, currentIndex, value, valueLength ) | ||
| + loopInvariant( __CPROVER_loop_entry( i ) <= i && i <= max && currentIndex < i ) | ||
| + decreases( max - i ) | ||
| { | ||
| if( nextValue( buf, &i, max, &value, &valueLength ) != true ) | ||
| { | ||
| @@ -1539,6 +1610,9 @@ static bool skipQueryPart( const char * buf, | ||
| @@ -1538,6 +1610,9 @@ static bool skipQueryPart( const char * buf, | ||
| while( ( i < max ) && | ||
| !isSeparator_( buf[ i ] ) && | ||
| !isSquareOpen_( buf[ i ] ) ) | ||
|
|
@@ -174,9 +182,9 @@ index d8694f0..761c44b 100644 | |
| { | ||
| i++; | ||
| } | ||
| @@ -1585,6 +1659,17 @@ static JSONStatus_t multiSearch( const char * buf, | ||
| @@ -1584,6 +1659,17 @@ static JSONStatus_t multiSearch( const char * buf, | ||
| coreJSON_ASSERT( ( max > 0U ) && ( queryLength > 0U ) ); | ||
|
|
||
| while( i < queryLength ) | ||
| + assigns( i, start, queryStart, value, length ) | ||
| + loopInvariant( | ||
|
|
@@ -191,4 +199,4 @@ index d8694f0..761c44b 100644 | |
| + decreases( queryLength - i ) | ||
| { | ||
| bool found = false; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -321,8 +321,6 @@ static uint8_t hexToInt( char c ) | |
| * | ||
| * @return true if a valid escape sequence was present; | ||
| * false otherwise. | ||
| * | ||
| * @note For the sake of security, \u0000 is disallowed. | ||
| */ | ||
| static bool skipOneHexEscape( const char * buf, | ||
| size_t * start, | ||
|
|
@@ -333,17 +331,16 @@ static bool skipOneHexEscape( const char * buf, | |
| size_t i = 0U, end = 0U; | ||
| uint16_t value = 0U; | ||
|
|
||
| coreJSON_ASSERT( ( buf != NULL ) && ( start != NULL ) && ( max > 0U ) ); | ||
| coreJSON_ASSERT( outValue != NULL ); | ||
| coreJSON_ASSERT( ( buf != NULL ) && ( start != NULL ) && ( max > 0U ) && ( outValue != NULL ) ); | ||
|
|
||
| i = *start; | ||
| #define HEX_ESCAPE_LENGTH ( 6U ) /* e.g., \u1234 */ | ||
| /* MISRA Ref 14.3.1 [Configuration dependent invariant] */ | ||
| /* More details at: https://github.com/FreeRTOS/coreJSON/blob/main/MISRA.md#rule-143 */ | ||
| /* coverity[misra_c_2012_rule_14_3_violation] */ | ||
| end = ( i <= ( SIZE_MAX - HEX_ESCAPE_LENGTH ) ) ? ( i + HEX_ESCAPE_LENGTH ) : SIZE_MAX; | ||
| end = i + HEX_ESCAPE_LENGTH; | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This previous logic relied on value remaning 0 to determine whether or not a hex escape was skipped. because the value 0 is now allowed, I have updated this logic to always add HEX_ESCAPE_LENGTH to i as the end and added an overflow check.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. that way the i == end logic holds for determining if a hex escape value was parsed. |
||
|
|
||
| if( ( end < max ) && ( buf[ i ] == '\\' ) && ( buf[ i + 1U ] == 'u' ) ) | ||
| if( ( end > i ) && ( end < max ) && ( buf[ i ] == '\\' ) && ( buf[ i + 1U ] == 'u' ) ) | ||
| { | ||
| for( i += 2U; i < end; i++ ) | ||
| { | ||
|
|
@@ -358,7 +355,7 @@ static bool skipOneHexEscape( const char * buf, | |
| } | ||
| } | ||
|
|
||
| if( ( i == end ) && ( value > 0U ) ) | ||
| if( i == end ) | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Allow value of 0 (\u0000) per JSON spec. |
||
| { | ||
| ret = true; | ||
| *outValue = value; | ||
|
|
@@ -382,8 +379,6 @@ static bool skipOneHexEscape( const char * buf, | |
| * | ||
| * @return true if a valid escape sequence was present; | ||
| * false otherwise. | ||
| * | ||
| * @note For the sake of security, \u0000 is disallowed. | ||
| */ | ||
| #define isHighSurrogate( x ) ( ( ( x ) >= 0xD800U ) && ( ( x ) <= 0xDBFFU ) ) | ||
| #define isLowSurrogate( x ) ( ( ( x ) >= 0xDC00U ) && ( ( x ) <= 0xDFFFU ) ) | ||
|
|
@@ -437,8 +432,6 @@ static bool skipHexEscape( const char * buf, | |
| * | ||
| * @return true if a valid escape sequence was present; | ||
| * false otherwise. | ||
| * | ||
| * @note For the sake of security, \NUL is disallowed. | ||
| */ | ||
| static bool skipEscape( const char * buf, | ||
| size_t * start, | ||
|
|
@@ -457,9 +450,6 @@ static bool skipEscape( const char * buf, | |
|
|
||
| switch( c ) | ||
| { | ||
| case '\0': | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This will break from the default case - no need for this. |
||
| break; | ||
|
|
||
| case 'u': | ||
| ret = skipHexEscape( buf, &i, max ); | ||
| break; | ||
|
|
@@ -477,14 +467,6 @@ static bool skipEscape( const char * buf, | |
| break; | ||
|
|
||
| default: | ||
|
|
||
| /* a control character: (NUL,SPACE) */ | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is invalid... escaped literal control characters should fail validation per spec, thus I have removed it. |
||
| if( iscntrl_( c ) ) | ||
| { | ||
| i += 2U; | ||
| ret = true; | ||
| } | ||
|
|
||
| break; | ||
| } | ||
| } | ||
|
|
@@ -934,11 +916,12 @@ static bool skipSpaceAndComma( const char * buf, | |
| * | ||
| * @note Stops advance if a value is an object or array. | ||
| */ | ||
| static void skipArrayScalars( const char * buf, | ||
| static bool skipArrayScalars( const char * buf, | ||
| size_t * start, | ||
| size_t max ) | ||
| { | ||
| size_t i = 0U; | ||
| bool ret = true; | ||
|
|
||
| coreJSON_ASSERT( ( buf != NULL ) && ( start != NULL ) && ( max > 0U ) ); | ||
|
|
||
|
|
@@ -953,11 +936,19 @@ static void skipArrayScalars( const char * buf, | |
|
|
||
| if( skipSpaceAndComma( buf, &i, max ) != true ) | ||
| { | ||
| /* After parsing a scalar, we must either have a comma (followed by more content) | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This fixes cases such as [3[4]] from being parsed as valid. |
||
| * or be at a closing bracket. If neither, the array is malformed. */ | ||
| if( ( i >= max ) || !isSquareClose_( buf[ i ] ) ) | ||
| { | ||
| ret = false; | ||
| } | ||
|
|
||
| break; | ||
| } | ||
| } | ||
|
|
||
| *start = i; | ||
| return ret; | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -1071,7 +1062,7 @@ static bool skipScalars( const char * buf, | |
| { | ||
| if( !isSquareClose_( buf[ i ] ) ) | ||
| { | ||
| skipArrayScalars( buf, start, max ); | ||
| ret = skipArrayScalars( buf, start, max ); | ||
| } | ||
| } | ||
| else | ||
|
|
@@ -1151,14 +1142,22 @@ static JSONStatus_t skipCollection( const char * buf, | |
| { | ||
| depth--; | ||
|
|
||
| if( ( skipSpaceAndComma( buf, &i, max ) == true ) && | ||
| isOpenBracket_( stack[ depth ] ) ) | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have removed this isOpenBracket_ check as there is no point... stack[ depth ] will always contain an open bracket when depth >= 0. |
||
| if( skipSpaceAndComma( buf, &i, max ) == true ) | ||
| { | ||
| if( skipScalars( buf, &i, max, stack[ depth ] ) != true ) | ||
| { | ||
| ret = JSONIllegalDocument; | ||
| } | ||
| } | ||
| else | ||
| { | ||
| /* After closing a nested collection, if there is no comma found when calling | ||
| * skipSpaceAndComma, then we must be at the end of the parent collection. */ | ||
| if( ( i < max ) && !isMatchingBracket_( stack[ depth ], buf[ i ] ) ) | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This fixes cases where missing commas between collections was considered valid. (e.g. [ { "key1" : "value1"} {"key2":"value2"}]) |
||
| { | ||
| ret = JSONIllegalDocument; | ||
| } | ||
| } | ||
|
|
||
| break; | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
to be frank here - I did not want to spend extra time reducing the complexity :)