diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 24d627f7..4a1bf926 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -8,34 +8,68 @@ on:
workflow_dispatch:
jobs:
- unittest:
+ unittests-sanitizer:
runs-on: ubuntu-latest
steps:
- name: Clone This Repo
uses: actions/checkout@v2
- - name: Build
+ - name: Build with Sanitizers
run: |
- sudo apt-get install -y lcov
- cmake -S test -B build/ \
+ sudo apt-get install -y cmake lcov
+ CFLAGS=" -O0 -Wall -Wextra"
+ CFLAGS+=" -Werror -Wno-error=pedantic"
+ CFLAGS+=" -D_FORTIFY_SOURCE=2"
+ CFLAGS+=" -Wformat"
+ CLFAGS+=" -Wformat-security"
+ CFLAGS+=" -Warray-bounds"
+ CFLAGS+=" -fsanitize=address,undefined"
+ CFLAGS+=" -fsanitize=pointer-compare -fsanitize=pointer-subtract"
+ CFLAGS+=" -fsanitize-recover=undefined"
+ CFLAGS+=" -fsanitize-address-use-after-scope"
+ CFLAGS+=" -fsanitize-undefined-trap-on-error"
+ CFLAGS_=" -fstack-protector-all"
+ cmake -S test -B build \
-G "Unix Makefiles" \
-DCMAKE_BUILD_TYPE=Debug \
- -DCMAKE_C_FLAGS='--coverage -Wall -Wextra -DNDEBUG'
- make -C build/ all
- - name: Test
+ -DCMAKE_VERBOSE_MAKEFILE:BOOL=ON \
+ -DCMAKE_C_FLAGS="${CFLAGS}"
+ make -C build all
+ - name: Run Tests with Sanitizers
run: |
- cd build/
+ cd build
+ make coverage
ctest -E system --output-on-failure
cd ..
- - name: Run Coverage
+ unittests-coverage:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Clone This Repo
+ uses: actions/checkout@v2
+ - name: Build Tests for Coverage
run: |
- make -C build/ coverage
+ sudo apt-get install -y cmake lcov
+ CFLAGS=" --coverage -O0 -Wall -Wextra"
+ CFLAGS+=" -Werror -Wno-error=pedantic"
+ CFLAGS+=" -DNDEBUG"
+ cmake -S test -B build_cov \
+ -G "Unix Makefiles" \
+ -DCMAKE_BUILD_TYPE=Debug \
+ -DCMAKE_VERBOSE_MAKEFILE:BOOL=ON \
+ -DCMAKE_C_FLAGS="${CFLAGS}"
+ make -C build_cov all
+ - name: Run Tests for Coverage
+ run: |
+ cd build_cov
+ make coverage
+ ctest -E system --output-on-failure
+ cd ..
declare -a EXCLUDE=("\*test\*" "\*CMakeCCompilerId\*" "\*mocks\*" "\*3rdparty\*")
- echo ${EXCLUDE[@]} | xargs lcov --rc lcov_branch_coverage=1 -r build/coverage.info -o build/coverage.info
- lcov --rc lcov_branch_coverage=1 --list build/coverage.info
+ echo ${EXCLUDE[@]} | xargs lcov --rc lcov_branch_coverage=1 -r build_cov/coverage.info -o build_cov/coverage.info
+ lcov --rc lcov_branch_coverage=1 --list build_cov/coverage.info
- name: Check Coverage
uses: FreeRTOS/CI-CD-Github-Actions/coverage-cop@main
with:
- path: ./build/coverage.info
+ path: ./build_cov/coverage.info
complexity:
runs-on: ubuntu-latest
steps:
diff --git a/docs/doxygen/include/size_table.html b/docs/doxygen/include/size_table.html
index c9873814..c98617e2 100644
--- a/docs/doxygen/include/size_table.html
+++ b/docs/doxygen/include/size_table.html
@@ -9,8 +9,8 @@
core_http_client.c |
- 3.1K |
- 2.5K |
+ 3.2K |
+ 2.6K |
http_parser.c (third-party utility) |
@@ -19,7 +19,7 @@
Total estimates |
- 18.8K |
- 15.5K |
+ 18.9K |
+ 15.6K |
diff --git a/lexicon.txt b/lexicon.txt
index 028e065e..624333ad 100644
--- a/lexicon.txt
+++ b/lexicon.txt
@@ -31,6 +31,7 @@ const
contentlength
convertint
copybrief
+copycharsuntilnull
copydoc
corehttp
coverity
@@ -70,7 +71,7 @@ html
http
httpclient
httpheadernotfound
-httpheaderstrncpy
+httpheadercpy
httpinsufficientmemory
httpinvalidparameter
httpinvalidresponse
@@ -128,6 +129,7 @@ loginfo
logwarn
mainpage
malloc
+maxlen
memcpy
memmove
methodlen
diff --git a/source/core_http_client.c b/source/core_http_client.c
index a3ba100a..4bd23f72 100644
--- a/source/core_http_client.c
+++ b/source/core_http_client.c
@@ -110,23 +110,37 @@ static HTTPStatus_t sendHttpBody( const TransportInterface_t * pTransport,
size_t reqBodyBufLen );
/**
- * @brief A strncpy replacement with HTTP header validation.
+ * @brief A strncpy replacement which works with char pointers and does not add
+ * a NULL terminator to the output buffer.
+ *
+ * @param[in] pDest The destination buffer to copy to.
+ * @param[in] pSrc The source buffer to copy from.
+ * @param[in] maxLen The maximum number of chars to copy from pSrc to @p pDest
+ *
+ * @return the number of chars written to @p pDest
+ */
+static size_t copyCharsUntilNull( char * pDest,
+ const char * pSrc,
+ size_t maxLen );
+
+/**
+ * @brief A memcpy replacement with HTTP header validation.
*
* This function checks for `\r` and `\n` in the @p pSrc while copying.
* This function checks for `:` in the @p pSrc, if @p isField is set to 1.
*
* @param[in] pDest The destination buffer to copy to.
* @param[in] pSrc The source buffer to copy from.
- * @param[in] len The length of @p pSrc to copy to pDest.
+ * @param[in] len The maximum number of character to copy from pSrc to @p pDest
* @param[in] isField Set to 0 to indicate that @p pSrc is a field. Set to 1 to
* indicate that @p pSrc is a value.
*
- * @return @p pDest if the copy was successful, NULL otherwise.
+ * @return the number of chars written to @p pDest
*/
-static char * httpHeaderStrncpy( char * pDest,
- const char * pSrc,
- size_t len,
- uint8_t isField );
+static size_t httpHeaderCpy( char * pDest,
+ const char * pSrc,
+ size_t len,
+ uint8_t isField );
/**
* @brief Write header based on parameters. This method also adds a trailing
@@ -221,7 +235,7 @@ static HTTPStatus_t sendHttpRequest( const TransportInterface_t * pTransport,
uint32_t sendFlags );
/**
- * @brief Converts an integer value to its ASCII representation in the passed
+ * @brief Converts an integer value to its ASCII representation in the given
* buffer.
*
* @param[in] value The value to convert to ASCII.
@@ -638,7 +652,7 @@ static int httpParserOnStatusCallback( http_parser * pHttpParser,
assert( pResponse != NULL );
/* Set the location of what to parse next. */
- pParsingContext->pBufferCur = pLoc + length;
+ pParsingContext->pBufferCur = &pLoc[ length ];
/* Initialize the first header field and value to be passed to the user
* callback. */
@@ -686,7 +700,7 @@ static int httpParserOnHeaderFieldCallback( http_parser * pHttpParser,
}
/* Set the location of what to parse next. */
- pParsingContext->pBufferCur = pLoc + length;
+ pParsingContext->pBufferCur = &pLoc[ length ];
/* The httpParserOnHeaderFieldCallback() always follows the
* httpParserOnHeaderValueCallback() if there is another header field. When
@@ -705,7 +719,7 @@ static int httpParserOnHeaderFieldCallback( http_parser * pHttpParser,
}
else
{
- assert( pParsingContext->lastHeaderFieldLen <= SIZE_MAX - length );
+ assert( pParsingContext->lastHeaderFieldLen <= ( SIZE_MAX - length ) );
pParsingContext->lastHeaderFieldLen += length;
}
@@ -732,7 +746,7 @@ static int httpParserOnHeaderValueCallback( http_parser * pHttpParser,
pParsingContext = ( HTTPParsingContext_t * ) ( pHttpParser->data );
/* Set the location of what to parse next. */
- pParsingContext->pBufferCur = pLoc + length;
+ pParsingContext->pBufferCur = &pLoc[ length ];
/* If httpParserOnHeaderValueCallback() is invoked in succession, then the
* last time http_parser_execute() was called only part of the header field
@@ -920,7 +934,7 @@ static int httpParserOnBodyCallback( http_parser * pHttpParser,
pResponse->bodyLen += length;
/* Set the next location of parsing. */
- pParsingContext->pBufferCur = pLoc + length;
+ pParsingContext->pBufferCur = &pLoc[ length ];
LogDebug( ( "Response parsing: Found the response body: "
"BodyLength=%lu",
@@ -1170,7 +1184,7 @@ static HTTPStatus_t parseHttpResponse( HTTPParsingContext_t * pParsingContext,
/* The next location to parse will always be after what has already
* been parsed. */
- pParsingContext->pBufferCur = parsingStartLoc + bytesParsed;
+ pParsingContext->pBufferCur = &parsingStartLoc[ bytesParsed ];
LogDebug( ( "Parsed HTTP Response buffer: BytesParsed=%lu, "
"ExpectedBytesParsed=%lu",
@@ -1237,21 +1251,52 @@ static uint8_t convertInt32ToAscii( int32_t value,
/*-----------------------------------------------------------*/
-static char * httpHeaderStrncpy( char * pDest,
- const char * pSrc,
- size_t len,
- uint8_t isField )
+static size_t copyCharsUntilNull( char * pDest,
+ const char * pSrc,
+ size_t maxLen )
{
size_t i = 0U;
- char * pRet = pDest;
+ size_t charsWritten = 0U;
+
+ assert( pDest != NULL );
+ assert( pSrc != NULL );
+
+ for( i = 0U; i < maxLen; i++ )
+ {
+ if( pSrc[ i ] == NULL_CHARACTER )
+ {
+ break;
+ }
+
+ pDest[ i ] = pSrc[ i ];
+ charsWritten++;
+ }
+
+ return charsWritten;
+}
+
+/*-----------------------------------------------------------*/
+
+static size_t httpHeaderCpy( char * pDest,
+ const char * pSrc,
+ size_t len,
+ uint8_t isField )
+{
+ size_t i = 0U;
+ size_t charsWritten = 0U;
uint8_t hasError = 0U;
+ uint8_t endOfInput = 0U;
assert( pDest != NULL );
assert( pSrc != NULL );
- for( ; i < len; i++ )
+ for( i = 0; i < len; i++ )
{
- if( pSrc[ i ] == CARRIAGE_RETURN_CHARACTER )
+ if( pSrc[ i ] == NULL_CHARACTER )
+ {
+ endOfInput = 1U;
+ }
+ else if( pSrc[ i ] == CARRIAGE_RETURN_CHARACTER )
{
LogError( ( "Invalid character '\r' found in %.*s",
( int ) len, pSrc ) );
@@ -1263,7 +1308,7 @@ static char * httpHeaderStrncpy( char * pDest,
( int ) len, pSrc ) );
hasError = 1U;
}
- else if( ( isField == 1U ) && ( pSrc[ i ] == COLON_CHARACTER ) )
+ else if( ( pSrc[ i ] == COLON_CHARACTER ) && ( isField == 1U ) )
{
LogError( ( "Invalid character ':' found in %.*s",
( int ) len, pSrc ) );
@@ -1272,16 +1317,21 @@ static char * httpHeaderStrncpy( char * pDest,
else
{
pDest[ i ] = pSrc[ i ];
+ charsWritten++;
}
- if( hasError == 1U )
+ if( ( endOfInput == 1U ) || ( hasError == 1U ) )
{
- pRet = NULL;
break;
}
}
- return pRet;
+ if( hasError == 1U )
+ {
+ charsWritten = 0U;
+ }
+
+ return charsWritten;
}
/*-----------------------------------------------------------*/
@@ -1293,9 +1343,9 @@ static HTTPStatus_t addHeader( HTTPRequestHeaders_t * pRequestHeaders,
size_t valueLen )
{
HTTPStatus_t returnStatus = HTTPSuccess;
- char * pBufferCur = NULL;
- size_t toAddLen = 0U;
- size_t backtrackHeaderLen = 0U;
+ char * pBufferStart = NULL;
+ ptrdiff_t toAddLen = 0U;
+ size_t bufferBacktrackLen = 0;
assert( pRequestHeaders != NULL );
assert( pRequestHeaders->pBuffer != NULL );
@@ -1304,66 +1354,72 @@ static HTTPStatus_t addHeader( HTTPRequestHeaders_t * pRequestHeaders,
assert( fieldLen != 0U );
assert( valueLen != 0U );
- pBufferCur = ( char * ) ( pRequestHeaders->pBuffer + pRequestHeaders->headersLen );
- backtrackHeaderLen = pRequestHeaders->headersLen;
-
/* Backtrack before trailing "\r\n" (HTTP header end) if it's already written.
* Note that this method also writes trailing "\r\n" before returning.
* The first condition prevents reading before start of the header. */
- if( ( HTTP_HEADER_END_INDICATOR_LEN <= pRequestHeaders->headersLen ) &&
- ( strncmp( ( char * ) pBufferCur - HTTP_HEADER_END_INDICATOR_LEN,
- HTTP_HEADER_END_INDICATOR, HTTP_HEADER_END_INDICATOR_LEN ) == 0 ) )
+ if( ( pRequestHeaders->headersLen >= HTTP_HEADER_END_INDICATOR_LEN ) &&
+ ( strncmp( ( char * ) &pRequestHeaders->pBuffer[ pRequestHeaders->headersLen - HTTP_HEADER_END_INDICATOR_LEN ],
+ HTTP_HEADER_END_INDICATOR,
+ HTTP_HEADER_END_INDICATOR_LEN ) == 0 ) )
{
- backtrackHeaderLen -= HTTP_HEADER_LINE_SEPARATOR_LEN;
- pBufferCur -= HTTP_HEADER_LINE_SEPARATOR_LEN;
+ /* Decrease the headersLen field */
+ pBufferStart = ( char * ) &pRequestHeaders->pBuffer[ pRequestHeaders->headersLen - HTTP_HEADER_LINE_SEPARATOR_LEN ];
+ bufferBacktrackLen = HTTP_HEADER_LINE_SEPARATOR_LEN;
+ }
+ else
+ {
+ pBufferStart = ( char * ) &pRequestHeaders->pBuffer[ pRequestHeaders->headersLen ];
}
- /* Check if there is enough space in buffer for additional header. */
- toAddLen = fieldLen + HTTP_HEADER_FIELD_SEPARATOR_LEN + valueLen +
+ /* Check if there is enough space in buffer for the additional header. */
+ toAddLen = ( ptrdiff_t ) fieldLen +
+ HTTP_HEADER_FIELD_SEPARATOR_LEN +
+ ( ptrdiff_t ) valueLen +
HTTP_HEADER_LINE_SEPARATOR_LEN +
HTTP_HEADER_LINE_SEPARATOR_LEN;
- /* If we have enough room for the new header line, then write it to the
- * header buffer. */
- if( ( backtrackHeaderLen + toAddLen ) <= pRequestHeaders->bufferLen )
+ assert(sizeof(ptrdiff_t) == 64 );
+ assert( toAddLen > fieldLen );
+ assert( toAddLen > valueLen );
+ assert( toAddLen > ( HTTP_HEADER_LINE_SEPARATOR_LEN + HTTP_HEADER_LINE_SEPARATOR_LEN + HTTP_HEADER_LINE_SEPARATOR_LEN ) );
+
+ /* If we have enough room for the additional header, then write it to the
+ * buffer. */
+ if( ( toAddLen + pRequestHeaders->headersLen - bufferBacktrackLen ) <= pRequestHeaders->bufferLen )
{
+ ptrdiff_t outBytesWritten = 0U;
/* Write ": \r\n" to the headers buffer. */
/* Copy the header name into the buffer. */
- if( httpHeaderStrncpy( pBufferCur, pField, fieldLen, HTTP_HEADER_STRNCPY_IS_FIELD ) == NULL )
- {
- returnStatus = HTTPSecurityAlertInvalidCharacter;
- }
-
- if( returnStatus == HTTPSuccess )
+ outBytesWritten += (ptrdiff_t) httpHeaderCpy( &pBufferStart[ outBytesWritten ],
+ pField,
+ fieldLen,
+ HTTP_HEADER_STRNCPY_IS_FIELD );
+
+ /* Copy the field separator, ": ", into the buffer. */
+ outBytesWritten += (ptrdiff_t) copyCharsUntilNull( &pBufferStart[ outBytesWritten ],
+ HTTP_HEADER_FIELD_SEPARATOR,
+ HTTP_HEADER_FIELD_SEPARATOR_LEN );
+
+ /* Copy the header value into the buffer. */
+ outBytesWritten += (ptrdiff_t) httpHeaderCpy( &pBufferStart[ outBytesWritten ],
+ pValue,
+ valueLen,
+ HTTP_HEADER_STRNCPY_IS_VALUE );
+
+ /* Copy the header end indicator, "\r\n\r\n" into the buffer. */
+ outBytesWritten += (ptrdiff_t) copyCharsUntilNull( &pBufferStart[ outBytesWritten ],
+ HTTP_HEADER_END_INDICATOR,
+ HTTP_HEADER_END_INDICATOR_LEN );
+
+ if( outBytesWritten == toAddLen )
{
- pBufferCur += fieldLen;
-
- /* Copy the field separator, ": ", into the buffer. */
- ( void ) strncpy( pBufferCur,
- HTTP_HEADER_FIELD_SEPARATOR,
- HTTP_HEADER_FIELD_SEPARATOR_LEN );
-
- pBufferCur += HTTP_HEADER_FIELD_SEPARATOR_LEN;
-
- /* Copy the header value into the buffer. */
- if( httpHeaderStrncpy( pBufferCur, pValue, valueLen, HTTP_HEADER_STRNCPY_IS_VALUE ) == NULL )
- {
- returnStatus = HTTPSecurityAlertInvalidCharacter;
- }
+ /* Update the headers length value only when everything is successful. */
+ pRequestHeaders->headersLen += ( outBytesWritten - bufferBacktrackLen );
}
-
- if( returnStatus == HTTPSuccess )
+ else
{
- pBufferCur += valueLen;
-
- /* Copy the header end indicator, "\r\n\r\n" into the buffer. */
- ( void ) strncpy( pBufferCur,
- HTTP_HEADER_END_INDICATOR,
- HTTP_HEADER_END_INDICATOR_LEN );
-
- /* Update the headers length value only when everything is successful. */
- pRequestHeaders->headersLen = backtrackHeaderLen + toAddLen;
+ returnStatus = HTTPSecurityAlertInvalidCharacter;
}
}
else
@@ -1387,7 +1443,7 @@ static HTTPStatus_t addRangeHeader( HTTPRequestHeaders_t * pRequestHeaders,
{
HTTPStatus_t returnStatus = HTTPSuccess;
char rangeValueBuffer[ HTTP_MAX_RANGE_REQUEST_VALUE_LEN ];
- size_t rangeValueLength = 0U;
+ ptrdiff_t rangeValueLength = 0U;
assert( pRequestHeaders != NULL );
@@ -1399,35 +1455,42 @@ static HTTPStatus_t addRangeHeader( HTTPRequestHeaders_t * pRequestHeaders,
/* Generate the value data for the Range Request header.*/
/* Write the range value prefix in the buffer. */
- ( void ) strncpy( rangeValueBuffer,
- HTTP_RANGE_REQUEST_HEADER_VALUE_PREFIX,
- HTTP_RANGE_REQUEST_HEADER_VALUE_PREFIX_LEN );
- rangeValueLength += HTTP_RANGE_REQUEST_HEADER_VALUE_PREFIX_LEN;
+ rangeValueLength += copyCharsUntilNull( rangeValueBuffer,
+ HTTP_RANGE_REQUEST_HEADER_VALUE_PREFIX,
+ HTTP_RANGE_REQUEST_HEADER_VALUE_PREFIX_LEN );
+
+ assert( rangeValueLength <= HTTP_MAX_RANGE_REQUEST_VALUE_LEN );
/* Write the range start value in the buffer. */
rangeValueLength += convertInt32ToAscii( rangeStartOrlastNbytes,
- rangeValueBuffer + rangeValueLength,
+ &rangeValueBuffer[ rangeValueLength ],
sizeof( rangeValueBuffer ) - rangeValueLength );
+ assert( rangeValueLength <= HTTP_MAX_RANGE_REQUEST_VALUE_LEN );
+
/* Add remaining value data depending on the range specification type. */
/* Add rangeEnd value if request is for [rangeStart, rangeEnd] byte range */
if( rangeEnd != HTTP_RANGE_REQUEST_END_OF_FILE )
{
/* Write the "-" character to the buffer.*/
- *( rangeValueBuffer + rangeValueLength ) = DASH_CHARACTER;
+ rangeValueBuffer[ rangeValueLength ] = DASH_CHARACTER;
rangeValueLength += DASH_CHARACTER_LEN;
+ assert( rangeValueLength <= HTTP_MAX_RANGE_REQUEST_VALUE_LEN );
+
/* Write the rangeEnd value of the request range to the buffer. */
rangeValueLength += convertInt32ToAscii( rangeEnd,
- rangeValueBuffer + rangeValueLength,
+ &rangeValueBuffer[ rangeValueLength ],
sizeof( rangeValueBuffer ) - rangeValueLength );
+
+ assert( rangeValueLength <= HTTP_MAX_RANGE_REQUEST_VALUE_LEN );
}
/* Case when request is for bytes in the range [rangeStart, EoF). */
else if( rangeStartOrlastNbytes >= 0 )
{
/* Write the "-" character to the buffer.*/
- *( rangeValueBuffer + rangeValueLength ) = DASH_CHARACTER;
+ rangeValueBuffer[ rangeValueLength ] = DASH_CHARACTER;
rangeValueLength += DASH_CHARACTER_LEN;
}
else
@@ -1455,17 +1518,18 @@ static HTTPStatus_t writeRequestLine( HTTPRequestHeaders_t * pRequestHeaders,
{
HTTPStatus_t returnStatus = HTTPSuccess;
char * pBufferCur = NULL;
- size_t toAddLen = 0U;
+ ptrdiff_t toAddLen = 0U;
+ ptrdiff_t outBytesWritten = 0U;
assert( pRequestHeaders != NULL );
assert( pRequestHeaders->pBuffer != NULL );
assert( pMethod != NULL );
assert( methodLen != 0U );
- toAddLen = methodLen + \
- SPACE_CHARACTER_LEN + \
- SPACE_CHARACTER_LEN + \
- HTTP_PROTOCOL_VERSION_LEN + \
+ toAddLen = ( ptrdiff_t ) methodLen +
+ SPACE_CHARACTER_LEN +
+ SPACE_CHARACTER_LEN +
+ HTTP_PROTOCOL_VERSION_LEN +
HTTP_HEADER_LINE_SEPARATOR_LEN;
pBufferCur = ( char * ) ( pRequestHeaders->pBuffer );
@@ -1479,38 +1543,43 @@ static HTTPStatus_t writeRequestLine( HTTPRequestHeaders_t * pRequestHeaders,
if( returnStatus == HTTPSuccess )
{
/* Write " HTTP/1.1\r\n" to start the HTTP header. */
- ( void ) strncpy( pBufferCur, pMethod, methodLen );
- pBufferCur += methodLen;
- *pBufferCur = SPACE_CHARACTER;
- pBufferCur += SPACE_CHARACTER_LEN;
+ outBytesWritten += copyCharsUntilNull( &pBufferCur[ outBytesWritten ], pMethod, methodLen );
+
+ pBufferCur[ outBytesWritten ] = SPACE_CHARACTER;
+ outBytesWritten += SPACE_CHARACTER_LEN;
/* Use "/" as default value if is NULL. */
if( ( pPath == NULL ) || ( pathLen == 0U ) )
{
- ( void ) strncpy( pBufferCur,
- HTTP_EMPTY_PATH,
- HTTP_EMPTY_PATH_LEN );
- pBufferCur += HTTP_EMPTY_PATH_LEN;
+ outBytesWritten += copyCharsUntilNull( &pBufferCur[ outBytesWritten ],
+ HTTP_EMPTY_PATH,
+ HTTP_EMPTY_PATH_LEN );
}
else
{
- ( void ) strncpy( pBufferCur, pPath, pathLen );
- pBufferCur += pathLen;
+ outBytesWritten += copyCharsUntilNull( &pBufferCur[ outBytesWritten ], pPath, pathLen );
}
- *pBufferCur = SPACE_CHARACTER;
- pBufferCur += SPACE_CHARACTER_LEN;
+ pBufferCur[ outBytesWritten ] = SPACE_CHARACTER;
+ outBytesWritten += SPACE_CHARACTER_LEN;
+
+ outBytesWritten += copyCharsUntilNull( &pBufferCur[ outBytesWritten ],
+ HTTP_PROTOCOL_VERSION,
+ HTTP_PROTOCOL_VERSION_LEN );
- ( void ) strncpy( pBufferCur,
- HTTP_PROTOCOL_VERSION,
- HTTP_PROTOCOL_VERSION_LEN );
- pBufferCur += HTTP_PROTOCOL_VERSION_LEN;
+ outBytesWritten += copyCharsUntilNull( &pBufferCur[ outBytesWritten ],
+ HTTP_HEADER_LINE_SEPARATOR,
+ HTTP_HEADER_LINE_SEPARATOR_LEN );
- ( void ) strncpy( pBufferCur,
- HTTP_HEADER_LINE_SEPARATOR,
- HTTP_HEADER_LINE_SEPARATOR_LEN );
- pRequestHeaders->headersLen = toAddLen;
+ if( outBytesWritten == toAddLen )
+ {
+ pRequestHeaders->headersLen = outBytesWritten;
+ }
+ else
+ {
+ returnStatus = HTTPInvalidParameter;
+ }
}
return returnStatus;
@@ -1746,10 +1815,10 @@ static HTTPStatus_t sendHttpData( const TransportInterface_t * pTransport,
size_t dataLen )
{
HTTPStatus_t returnStatus = HTTPSuccess;
- const uint8_t * pIndex = pData;
+ size_t dataIndex = 0U;
int32_t bytesSent = 0;
- size_t bytesRemaining = dataLen;
- uint32_t lastSendTimeMs = 0U, timeSinceLastSendMs = 0U;
+ uint32_t lastSendTimeMs = 0U;
+ uint32_t timeSinceLastSendMs = 0U;
uint32_t retryTimeoutMs = HTTP_SEND_RETRY_TIMEOUT_MS;
assert( pTransport != NULL );
@@ -1768,11 +1837,11 @@ static HTTPStatus_t sendHttpData( const TransportInterface_t * pTransport,
lastSendTimeMs = getTimestampMs();
/* Loop until all data is sent. */
- while( ( bytesRemaining > 0UL ) && ( returnStatus != HTTPNetworkError ) )
+ while( ( ( dataLen - dataIndex ) > 0UL ) && ( returnStatus != HTTPNetworkError ) )
{
bytesSent = pTransport->send( pTransport->pNetworkContext,
- pIndex,
- bytesRemaining );
+ &pData[ dataIndex ],
+ ( dataLen - dataIndex ) );
/* BytesSent less than zero is an error. */
if( bytesSent < 0 )
@@ -1785,20 +1854,20 @@ static HTTPStatus_t sendHttpData( const TransportInterface_t * pTransport,
{
/* It is a bug in the application's transport send implementation if
* more bytes than expected are sent. To avoid a possible overflow
- * in converting bytesRemaining from unsigned to signed, this assert
- * must exist after the check for bytesSent being negative. */
- assert( ( size_t ) bytesSent <= bytesRemaining );
+ * in converting the third argument from unsigned to signed, this
+ * assert must exist after the check for bytesSent overflowing when
+ * cast to a size_t. */
+ assert( ( size_t ) bytesSent <= ( dataLen - dataIndex ) );
/* Record the most recent time of successful transmission. */
lastSendTimeMs = getTimestampMs();
- bytesRemaining -= ( size_t ) bytesSent;
- pIndex += bytesSent;
+ dataIndex += ( size_t ) bytesSent;
LogDebug( ( "Sent data over the transport: "
"BytesSent=%ld, BytesRemaining=%lu, TotalBytesSent=%lu",
( long int ) bytesSent,
- ( unsigned long ) bytesRemaining,
- ( unsigned long ) ( dataLen - bytesRemaining ) ) );
+ ( unsigned long ) ( dataLen - dataIndex ),
+ ( unsigned long ) dataIndex ) );
}
else
{
@@ -1917,8 +1986,8 @@ static HTTPStatus_t getFinalResponseStatus( HTTPParsingState_t parsingState,
{
HTTPStatus_t returnStatus = HTTPSuccess;
- assert( parsingState >= HTTP_PARSING_NONE &&
- parsingState <= HTTP_PARSING_COMPLETE );
+ assert( ( parsingState >= HTTP_PARSING_NONE ) &&
+ ( parsingState <= HTTP_PARSING_COMPLETE ) );
assert( totalReceived <= responseBufferLen );
/* If no parsing occurred, that means network data was never received. */
@@ -1966,8 +2035,11 @@ static HTTPStatus_t receiveAndParseHttpResponse( const TransportInterface_t * pT
size_t totalReceived = 0U;
int32_t currentReceived = 0;
HTTPParsingContext_t parsingContext = { 0 };
- uint8_t shouldRecv = 1U, shouldParse = 1U, timeoutReached = 0U;
- uint32_t lastRecvTimeMs = 0U, timeSinceLastRecvMs = 0U;
+ uint8_t shouldRecv = 1U;
+ uint8_t shouldParse = 1U;
+ uint8_t timeoutReached = 0U;
+ uint32_t lastRecvTimeMs = 0U;
+ uint32_t timeSinceLastRecvMs = 0U;
uint32_t retryTimeoutMs = HTTP_RECV_RETRY_TIMEOUT_MS;
assert( pTransport != NULL );
@@ -2017,7 +2089,7 @@ static HTTPStatus_t receiveAndParseHttpResponse( const TransportInterface_t * pT
* Because we cannot know how large the HTTP response will be in
* total, parsing will tell us if the end of the message is reached.*/
shouldParse = 1U;
- totalReceived += currentReceived;
+ totalReceived += ( size_t ) currentReceived;
}
else
{
@@ -2084,7 +2156,7 @@ static HTTPStatus_t sendHttpRequest( const TransportInterface_t * pTransport,
assert( pTransport != NULL );
assert( pRequestHeaders != NULL );
assert( ( pRequestBodyBuf != NULL ) ||
- ( ( pRequestBodyBuf == NULL ) && ( reqBodyBufLen == 0 ) ) );
+ ( ( pRequestBodyBuf == NULL ) && ( reqBodyBufLen == 0U ) ) );
assert( getTimestampMs != NULL );
/* Send the headers, which are at one location in memory. */
diff --git a/source/include/core_http_client_private.h b/source/include/core_http_client_private.h
index d87a7ddf..a3340304 100644
--- a/source/include/core_http_client_private.h
+++ b/source/include/core_http_client_private.h
@@ -59,15 +59,16 @@
#define CARRIAGE_RETURN_CHARACTER '\r' /**< A carriage return character to help with header validation. */
#define LINEFEED_CHARACTER '\n' /**< A linefeed character to help with header validation. */
#define COLON_CHARACTER ':' /**< A colon character to help with header validation. */
+#define NULL_CHARACTER '\0' /**< A NULL character to help with header validation. */
/**
- * @brief Indicator for function #httpHeaderStrncpy that the pSrc parameter is a
+ * @brief Indicator for function #httpHeaderCpy that the pSrc parameter is a
* header value.
*/
#define HTTP_HEADER_STRNCPY_IS_VALUE 0U
/**
- * @brief Indicator for function #httpHeaderStrncpy that the pSrc parameter is a
+ * @brief Indicator for function #httpHeaderCpy that the pSrc parameter is a
* header field.
*/
#define HTTP_HEADER_STRNCPY_IS_FIELD 1U
diff --git a/test/cbmc/proofs/HTTPClient_AddHeader/Makefile b/test/cbmc/proofs/HTTPClient_AddHeader/Makefile
index 7394ec1f..6a16665f 100644
--- a/test/cbmc/proofs/HTTPClient_AddHeader/Makefile
+++ b/test/cbmc/proofs/HTTPClient_AddHeader/Makefile
@@ -44,8 +44,8 @@ REMOVE_FUNCTION_BODY += __CPROVER_file_local_core_http_client_c_httpParserOnMess
# This decreases the run-time of the proof. This is safe to do for this proof
# because HTTPClient_AddHeader does not use the results of the copy later in the
# function.
-REMOVE_FUNCTION_BODY += strncpy
-REMOVE_FUNCTION_BODY += __CPROVER_file_local_core_http_client_c_httpHeaderStrncpy
+REMOVE_FUNCTION_BODY += __CPROVER_file_local_core_http_client_c_copyCharsUntilNull
+REMOVE_FUNCTION_BODY += __CPROVER_file_local_core_http_client_c_httpHeaderCpy
# strncmp is used to find if there exists "\r\n\r\n" at the end of the header
# buffer. Therefore, we need to unwind strncmp the length of "\r\n\r\n" + 1.
@@ -53,8 +53,8 @@ UNWINDSET += strncmp.0:5
PROOF_SOURCES += $(PROOFDIR)/$(HARNESS_FILE).c
PROOF_SOURCES += $(SRCDIR)/test/cbmc/sources/http_cbmc_state.c
-PROOF_SOURCES += $(SRCDIR)/test/cbmc/stubs/strncpy.c
-PROOF_SOURCES += $(SRCDIR)/test/cbmc/stubs/httpHeaderStrncpy.c
+PROOF_SOURCES += $(SRCDIR)/test/cbmc/stubs/copyCharsUntilNull.c
+PROOF_SOURCES += $(SRCDIR)/test/cbmc/stubs/httpHeaderCpy.c
PROJECT_SOURCES += $(SRCDIR)/source/core_http_client.c
diff --git a/test/cbmc/proofs/HTTPClient_AddRangeHeader/Makefile b/test/cbmc/proofs/HTTPClient_AddRangeHeader/Makefile
index 71fcf7c5..952465fb 100644
--- a/test/cbmc/proofs/HTTPClient_AddRangeHeader/Makefile
+++ b/test/cbmc/proofs/HTTPClient_AddRangeHeader/Makefile
@@ -31,8 +31,8 @@ INCLUDES +=
# This decreases the run-time of the proof. This is safe to do for this proof
# because HTTPClient_AddRangeHeader does not use the results of the copy later
# in the function.
-REMOVE_FUNCTION_BODY += strncpy
-REMOVE_FUNCTION_BODY += __CPROVER_file_local_core_http_client_c_httpHeaderStrncpy
+REMOVE_FUNCTION_BODY += __CPROVER_file_local_core_http_client_c_copyCharsUntilNull
+REMOVE_FUNCTION_BODY += __CPROVER_file_local_core_http_client_c_httpHeaderCpy
# strncmp is used to find if there exists "\r\n\r\n" at the end of the header
# buffer. Therefore, we need to unwind strncmp the length of "\r\n\r\n" + 1.
@@ -43,8 +43,8 @@ UNWINDSET += __CPROVER_file_local_core_http_client_c_convertInt32ToAscii.1:11
PROOF_SOURCES += $(PROOFDIR)/$(HARNESS_FILE).c
PROOF_SOURCES += $(SRCDIR)/test/cbmc/sources/http_cbmc_state.c
-PROOF_SOURCES += $(SRCDIR)/test/cbmc/stubs/strncpy.c
-PROOF_SOURCES += $(SRCDIR)/test/cbmc/stubs/httpHeaderStrncpy.c
+PROOF_SOURCES += $(SRCDIR)/test/cbmc/stubs/copyCharsUntilNull.c
+PROOF_SOURCES += $(SRCDIR)/test/cbmc/stubs/httpHeaderCpy.c
PROJECT_SOURCES += $(SRCDIR)/source/core_http_client.c
diff --git a/test/cbmc/proofs/HTTPClient_InitializeRequestHeaders/Makefile b/test/cbmc/proofs/HTTPClient_InitializeRequestHeaders/Makefile
index e8c8208d..7520cac2 100644
--- a/test/cbmc/proofs/HTTPClient_InitializeRequestHeaders/Makefile
+++ b/test/cbmc/proofs/HTTPClient_InitializeRequestHeaders/Makefile
@@ -43,8 +43,8 @@ REMOVE_FUNCTION_BODY += __CPROVER_file_local_core_http_client_c_httpParserOnMess
# This decreases the run-time of the proof. This is safe to do for this proof
# because HTTPClient_InitializeRequestHeaders does not use the results of the
# copy later in the function.
-REMOVE_FUNCTION_BODY += strncpy
-REMOVE_FUNCTION_BODY += __CPROVER_file_local_core_http_client_c_httpHeaderStrncpy
+REMOVE_FUNCTION_BODY += __CPROVER_file_local_core_http_client_c_copyCharsUntilNull
+REMOVE_FUNCTION_BODY += __CPROVER_file_local_core_http_client_c_httpHeaderCpy
# strncmp is used to find if there exists "\r\n\r\n" at the end of the header
# buffer. Therefore, we need to unwind strncmp the length of "\r\n\r\n" + 1.
@@ -52,8 +52,8 @@ UNWINDSET += strncmp.0:5
PROOF_SOURCES += $(PROOFDIR)/$(HARNESS_FILE).c
PROOF_SOURCES += $(SRCDIR)/test/cbmc/sources/http_cbmc_state.c
-PROOF_SOURCES += $(SRCDIR)/test/cbmc/stubs/strncpy.c
-PROOF_SOURCES += $(SRCDIR)/test/cbmc/stubs/httpHeaderStrncpy.c
+PROOF_SOURCES += $(SRCDIR)/test/cbmc/stubs/copyCharsUntilNull.c
+PROOF_SOURCES += $(SRCDIR)/test/cbmc/stubs/httpHeaderCpy.c
PROJECT_SOURCES += $(SRCDIR)/source/core_http_client.c
diff --git a/test/cbmc/proofs/HTTPClient_Send/Makefile b/test/cbmc/proofs/HTTPClient_Send/Makefile
index fdeb4daf..b72f4e8a 100644
--- a/test/cbmc/proofs/HTTPClient_Send/Makefile
+++ b/test/cbmc/proofs/HTTPClient_Send/Makefile
@@ -51,8 +51,8 @@ REMOVE_FUNCTION_BODY += http_parser_settings_init
# This decreases the run-time of the proof. This is safe to do for this proof
# because all of the functions, in HTTPClient_Send, that would have used the
# results of the copy are stubbed out to be proven separately.
-REMOVE_FUNCTION_BODY += strncpy
-REMOVE_FUNCTION_BODY += __CPROVER_file_local_core_http_client_c_httpHeaderStrncpy
+REMOVE_FUNCTION_BODY += __CPROVER_file_local_core_http_client_c_copyCharsUntilNull
+REMOVE_FUNCTION_BODY += __CPROVER_file_local_core_http_client_c_httpHeaderCpy
# There is a total of 10 digits in INT32_MAX. These loops are unwound once more
# than the total possible iterations in the int32_t to ASCII converation.
@@ -70,8 +70,8 @@ PROOF_SOURCES += $(SRCDIR)/test/cbmc/sources/http_cbmc_state.c
PROOF_SOURCES += $(SRCDIR)/test/cbmc/stubs/HTTPClient_Send_http_parser_execute.c
PROOF_SOURCES += $(SRCDIR)/test/cbmc/stubs/transport_interface_stubs.c
PROOF_SOURCES += $(SRCDIR)/test/cbmc/stubs/get_time_stub.c
-PROOF_SOURCES += $(SRCDIR)/test/cbmc/stubs/strncpy.c
-PROOF_SOURCES += $(SRCDIR)/test/cbmc/stubs/httpHeaderStrncpy.c
+PROOF_SOURCES += $(SRCDIR)/test/cbmc/stubs/copyCharsUntilNull.c
+PROOF_SOURCES += $(SRCDIR)/test/cbmc/stubs/httpHeaderCpy.c
PROJECT_SOURCES += $(SRCDIR)/source/core_http_client.c
diff --git a/test/cbmc/stubs/strncpy.c b/test/cbmc/stubs/copyCharsUntilNull.c
similarity index 51%
rename from test/cbmc/stubs/strncpy.c
rename to test/cbmc/stubs/copyCharsUntilNull.c
index 353a51ab..413bcada 100644
--- a/test/cbmc/stubs/strncpy.c
+++ b/test/cbmc/stubs/copyCharsUntilNull.c
@@ -21,37 +21,22 @@
*/
/**
- * @file strncpy.c
- * @brief Creates a stub for strncpy so that the proofs for HTTPClient_AddHeader,
- * HTTPClient_AddRangeHeader, and HTTPClient_InitializeRequestHeaders, run much
- * faster. This stub checks if, for the input copy length, the destination and
- * source are valid accessible memory.
+ * @file copyCharsUntilNull.c
+ * @brief Creates a stub for copyCharsUntilNull so that the proofs for
+ * HTTPClient_AddHeader, HTTPClient_AddRangeHeader, and
+ * HTTPClient_InitializeRequestHeaders run much faster. This stub checks if, for
+ * the input copy length, the destination and source are valid accessible
+ * memory.
*/
#include
+#include
-/* This is a clang macro not available on linux */
-#ifndef __has_builtin
- #define __has_builtin( x ) 0
-#endif
-
-#if __has_builtin( __builtin___strncpy_chk )
- void * __builtin___strncpy_chk( void * dest,
- const void * src,
- size_t n,
- size_t os )
- {
- __CPROVER_assert( __CPROVER_w_ok( dest, n ), "write" );
- __CPROVER_assert( __CPROVER_r_ok( src, n ), "read" );
- return dest;
- }
-#else
- void * strncpy( void * dest,
- const void * src,
- size_t n )
- {
- __CPROVER_assert( __CPROVER_w_ok( dest, n ), "write" );
- __CPROVER_assert( __CPROVER_r_ok( src, n ), "read" );
- return dest;
- }
-#endif /* if __has_builtin( __builtin___strncpy_chk ) */
+size_t copyCharsUntilNull( char * pDest,
+ const char * pSrc,
+ size_t maxLen )
+{
+ __CPROVER_assert( __CPROVER_w_ok( pDest, maxLen ), "write" );
+ __CPROVER_assert( __CPROVER_r_ok( pSrc, maxLen ), "read" );
+ return maxLen;
+}
diff --git a/test/cbmc/stubs/httpHeaderStrncpy.c b/test/cbmc/stubs/httpHeaderCpy.c
similarity index 84%
rename from test/cbmc/stubs/httpHeaderStrncpy.c
rename to test/cbmc/stubs/httpHeaderCpy.c
index ce6aa796..0ceafac3 100644
--- a/test/cbmc/stubs/httpHeaderStrncpy.c
+++ b/test/cbmc/stubs/httpHeaderCpy.c
@@ -21,8 +21,8 @@
*/
/**
- * @file httpHeaderStrncpy.c
- * @brief Creates a stub for httpHeaderStrncpy so that the proofs for
+ * @file httpHeaderCpy.c
+ * @brief Creates a stub for httpHeaderCpy so that the proofs for
* HTTPClient_AddHeader, HTTPClient_AddRangeHeader, and
* HTTPClient_InitializeRequestHeaders run much faster. This stub checks if, for
* the input copy length, the destination and source are valid accessible
@@ -32,12 +32,12 @@
#include
#include
-void * httpHeaderStrncpy( char * pDest,
- const char * pSrc,
- size_t len,
- uint8_t isField )
+size_t httpHeaderCpy( char * pDest,
+ const char * pSrc,
+ size_t len,
+ uint8_t isField )
{
__CPROVER_assert( __CPROVER_w_ok( pDest, len ), "write" );
__CPROVER_assert( __CPROVER_r_ok( pSrc, len ), "read" );
- return pDest;
+ return len;
}
diff --git a/test/unit-test/core_http_utest.c b/test/unit-test/core_http_utest.c
index b3e8ff24..4a57096d 100644
--- a/test/unit-test/core_http_utest.c
+++ b/test/unit-test/core_http_utest.c
@@ -592,6 +592,33 @@ void test_Http_InitializeRequestHeaders_Insufficient_Memory()
HTTP_TEST_REQUEST_LINE_LEN ) != 0 );
}
+/**
+ * @brief Test HTTPInsufficientMemory with a path containing a null byte.
+ */
+void test_Http_InitializeRequestHeaders_NullByteInPath()
+{
+ HTTPStatus_t httpStatus = HTTPSuccess;
+ HTTPRequestHeaders_t requestHeaders = { 0 };
+ HTTPRequestInfo_t requestInfo = { 0 };
+ const char * const pathWithNullByte = "/AB\0CDEF/";
+
+ expectedHeaders.dataLen = HTTP_TEST_PREFIX_HEADER_LEN -
+ HTTP_TEST_REQUEST_PATH_LEN +
+ HTTP_EMPTY_PATH_LEN;
+
+ setupRequestInfo( &requestInfo );
+ setupBuffer( &requestHeaders );
+
+ requestInfo.pPath = pathWithNullByte;
+ requestInfo.pathLen = ( sizeof( pathWithNullByte ) - 1 );
+ requestInfo.reqFlags = 0U;
+
+ requestHeaders.pBuffer = testBuffer;
+ requestHeaders.bufferLen = expectedHeaders.dataLen;
+ httpStatus = HTTPClient_InitializeRequestHeaders( &requestHeaders, &requestInfo );
+ TEST_ASSERT_EQUAL( HTTPInvalidParameter, httpStatus );
+}
+
/* ===================== Testing HTTPClient_AddHeader ======================= */
/**
@@ -827,6 +854,7 @@ void test_Http_AddHeader_Invalid_Fields()
const char * colonInField = "head:er-field";
const char * linefeedInField = "head\ner-field";
const char * carriageReturnInField = "head\rer-field";
+ const char * nullInField = "head\0er-field";
setupBuffer( &requestHeaders );
@@ -851,6 +879,11 @@ void test_Http_AddHeader_Invalid_Fields()
carriageReturnInField, strlen( carriageReturnInField ),
HTTP_TEST_HEADER_VALUE, HTTP_TEST_HEADER_VALUE_LEN );
TEST_ASSERT_EQUAL( HTTPSecurityAlertInvalidCharacter, httpStatus );
+
+ httpStatus = HTTPClient_AddHeader( &requestHeaders,
+ nullInField, sizeof( nullInField ),
+ HTTP_TEST_HEADER_VALUE, HTTP_TEST_HEADER_VALUE_LEN );
+ TEST_ASSERT_EQUAL( HTTPSecurityAlertInvalidCharacter, httpStatus );
}
/**
@@ -865,6 +898,7 @@ void test_Http_AddHeader_Invalid_Values()
const char * colonInValue = "head:er-value";
const char * linefeedInValue = "head\ner-Value";
const char * carriageReturnInValue = "head\rer-Value";
+ const char * nullInValue = "head\0er-Value";
setupBuffer( &requestHeaders );
@@ -909,6 +943,11 @@ void test_Http_AddHeader_Invalid_Values()
HTTP_TEST_HEADER_FIELD, HTTP_TEST_HEADER_FIELD_LEN,
carriageReturnInValue, strlen( carriageReturnInValue ) );
TEST_ASSERT_EQUAL( HTTPSecurityAlertInvalidCharacter, httpStatus );
+
+ httpStatus = HTTPClient_AddHeader( &requestHeaders,
+ HTTP_TEST_HEADER_FIELD, HTTP_TEST_HEADER_FIELD_LEN,
+ carriageReturnInValue, sizeof( nullInValue ) - 1 );
+ TEST_ASSERT_EQUAL( HTTPSecurityAlertInvalidCharacter, httpStatus );
}
/* ============== Testing HTTPClient_AddRangeHeader ================== */
@@ -996,6 +1035,7 @@ void test_Http_AddRangeHeader_Insufficient_Memory( void )
&expectedHeaders,
PREEXISTING_HEADER_DATA );
size_t preHeadersLen = testHeaders.headersLen;
+
testRangeStart = 5;
testRangeEnd = 10;