Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/MicroOcpp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -547,7 +547,7 @@ bool endTransaction(const char *idTag, const char *reason, unsigned int connecto
}
return;
}
if (idTagInfo.containsKey("parentIdTag") && !strcmp(idTagInfo["parenIdTag"], tx->getParentIdTag()))
if (idTagInfo.containsKey("parentIdTag") && !strcmp(idTagInfo["parentIdTag"], tx->getParentIdTag()))
{
endTransaction_authorized(idTag_capture.c_str(), reason_capture.empty() ? (const char*)nullptr : reason_capture.c_str(), connectorId);
}
Expand Down
94 changes: 94 additions & 0 deletions src/MicroOcpp/Core/Configuration.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

#include <string.h>
#include <algorithm>
#include <climits>
#include <ArduinoJson.h>

namespace MicroOcpp {
Expand Down Expand Up @@ -255,4 +256,97 @@ bool VALIDATE_UNSIGNED_INT(const char *value) {
return true;
}

bool safeStringToInt(const char* str, int* result) {
if (!str || !result) {
return false;
}

// Skip leading whitespace
while (*str == ' ' || *str == '\t') {
str++;
}

// Check for empty string
if (*str == '\0') {
return false;
}

// Handle sign
bool negative = false;
if (*str == '-') {
negative = true;
str++;
} else if (*str == '+') {
str++;
}

// Check if there are digits after sign
if (*str < '0' || *str > '9') {
return false;
}

// Count digits and validate characters BEFORE parsing
int nDigits = 0;
const char* p = str;
while (*p >= '0' && *p <= '9') {
nDigits++;
p++;
}

// Check for trailing non-digit characters (allow decimal point for compatibility)
if (*p != '\0' && *p != '.') {
return false;
}

// Determine maximum safe digit count based on int size
int INT_MAXDIGITS;
if (sizeof(int) >= 4UL) {
INT_MAXDIGITS = 10; // Safe range: allows valid 10-digit integers within INT_MAX
} else {
INT_MAXDIGITS = 4; // Safe range: -9,999 to 9,999
}

// Reject if too many digits (prevents overflow)
if (nDigits > INT_MAXDIGITS) {
return false;
}

// Now perform the actual conversion with overflow checking
int value = 0;
const int INT_MAX_DIV_10 = INT_MAX / 10;
const int INT_MIN_DIV_10 = INT_MIN / 10;

while (*str >= '0' && *str <= '9') {
int digit = *str - '0';

if (negative) {
// Check for overflow before multiplication
if (value < INT_MIN_DIV_10) {
return false;
}
value *= 10;
// Check for overflow before subtraction
if (value < INT_MIN + digit) {
return false;
}
value -= digit;
} else {
// Check for overflow before multiplication
if (value > INT_MAX_DIV_10) {
return false;
}
value *= 10;
// Check for overflow before addition
if (value > INT_MAX - digit) {
return false;
}
value += digit;
}
str++;
}

*result = value;
return true;
}

} //end namespace MicroOcpp
3 changes: 3 additions & 0 deletions src/MicroOcpp/Core/Configuration.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,5 +41,8 @@ bool configuration_clean_unused(); //remove configs which haven't been accessed
//default implementation for common validator
bool VALIDATE_UNSIGNED_INT(const char*);

//safely convert string to int with overflow protection; returns false on null, invalid format, or overflow
bool safeStringToInt(const char *str, int *result);

} //end namespace MicroOcpp
#endif
6 changes: 6 additions & 0 deletions src/MicroOcpp/Core/FtpMbedTLS.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -892,6 +892,12 @@ bool FtpTransferMbedTLS::read_url_data(const char *data_url_raw) {
return false;
}

// Validate that all values are valid octets (0-255)
if (h1 > 255 || h2 > 255 || h3 > 255 || h4 > 255 || p1 > 255 || p2 > 255) {
MO_DBG_ERR("PASV response contains invalid octet value");
return false;
}

unsigned int port = 256U * p1 + p2;

char buf [64] = {'\0'};
Expand Down
29 changes: 28 additions & 1 deletion src/MicroOcpp/Core/Request.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -115,17 +115,32 @@ Request::CreateRequestResult Request::createRequest(JsonDoc& requestJson) {
}

bool Request::receiveResponse(JsonArray response){

/*
* Validate array size to prevent NULL pointer dereference
*/
if (response.size() < 2) {
MO_DBG_ERR("malformed response: insufficient elements (size=%zu, expected>=2)", response.size());
return false;
}

/*
* check if messageIDs match. If yes, continue with this function. If not, return false for message not consumed
*/
if (messageID.compare(response[1].as<const char*>())){
if (!response[1].is<const char*>() || messageID.compare(response[1].as<const char*>())) {
return false;
}

int messageTypeId = response[0] | -1;

if (messageTypeId == MESSAGE_TYPE_CALLRESULT) {

// CALLRESULT format: [3, messageId, payload] - requires 3 elements
if (response.size() < 3) {
MO_DBG_ERR("malformed CALLRESULT: insufficient elements (size=%zu, expected>=3)", response.size());
return false;
}

/*
* Hand the payload over to the Operation object
*/
Expand All @@ -143,6 +158,12 @@ bool Request::receiveResponse(JsonArray response){
return true;
} else if (messageTypeId == MESSAGE_TYPE_CALLERROR) {

// CALLERROR format: [4, messageId, errorCode, errorDescription, errorDetails] - requires 5 elements
if (response.size() < 5) {
MO_DBG_ERR("malformed CALLERROR: insufficient elements (size=%zu, expected>=5)", response.size());
return false;
}

/*
* Hand the error over to the Operation object
*/
Expand All @@ -166,6 +187,12 @@ bool Request::receiveResponse(JsonArray response){

bool Request::receiveRequest(JsonArray request) {

// CALL format: [2, messageId, action, payload] - requires 4 elements
if (request.size() < 4) {
MO_DBG_ERR("malformed request: insufficient elements (size=%zu, expected>=4)", request.size());
return false;
}

if (!request[1].is<const char*>()) {
MO_DBG_ERR("malformatted msgId");
return false;
Expand Down
31 changes: 29 additions & 2 deletions src/MicroOcpp/Core/RequestQueue.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -294,16 +294,27 @@ bool RequestQueue::receiveMessage(const char* payload, size_t length) {
int messageTypeId = doc[0] | -1;

if (messageTypeId == MESSAGE_TYPE_CALL) {
receiveRequest(doc.as<JsonArray>());
// Validate minimum array size for CALL messages
if (doc.as<JsonArray>().size() < 4) {
MO_DBG_WARN("Invalid OCPP CALL message: insufficient elements");
break;
}
receiveRequest(doc.as<JsonArray>());
success = true;
} else if (messageTypeId == MESSAGE_TYPE_CALLRESULT ||
messageTypeId == MESSAGE_TYPE_CALLERROR) {
// Validate minimum array size for response messages
size_t minSize = (messageTypeId == MESSAGE_TYPE_CALLRESULT) ? 3 : 5;
if (doc.as<JsonArray>().size() < minSize) {
MO_DBG_WARN("Invalid OCPP response message: insufficient elements");
break;
}
receiveResponse(doc.as<JsonArray>());
success = true;
} else {
MO_DBG_WARN("Invalid OCPP message! (though JSON has successfully been deserialized)");
}
break;
break;
}
case DeserializationError::InvalidInput:
MO_DBG_WARN("Invalid input! Not a JSON");
Expand All @@ -324,11 +335,22 @@ bool RequestQueue::receiveMessage(const char* payload, size_t length) {
if (err2.code() == DeserializationError::Ok) {
int messageTypeId = doc[0] | -1;
if (messageTypeId == MESSAGE_TYPE_CALL) {
// Validate minimum array size
if (doc.as<JsonArray>().size() < 4) {
MO_DBG_WARN("Invalid OCPP CALL message: insufficient elements");
break;
}
success = true;
auto op = makeRequest(new MsgBufferExceeded(MO_MAX_JSON_CAPACITY, length));
receiveRequest(doc.as<JsonArray>(), std::move(op));
} else if (messageTypeId == MESSAGE_TYPE_CALLRESULT ||
messageTypeId == MESSAGE_TYPE_CALLERROR) {
// Validate minimum array size
size_t minSize = (messageTypeId == MESSAGE_TYPE_CALLRESULT) ? 3 : 5;
if (doc.as<JsonArray>().size() < minSize) {
MO_DBG_WARN("Invalid OCPP response message: insufficient elements");
break;
}
success = true;
MO_DBG_WARN("crop incoming response");
receiveResponse(doc.as<JsonArray>());
Expand Down Expand Up @@ -361,6 +383,11 @@ void RequestQueue::receiveResponse(JsonArray json) {
}

void RequestQueue::receiveRequest(JsonArray json) {
// Validate minimum array size before accessing json[2]
if (json.size() < 3) {
MO_DBG_WARN("malformed request: insufficient elements for action field");
return;
}
auto op = operationRegistry.deserializeOperation(json[2] | "UNDEFINED");
if (op == nullptr) {
MO_DBG_WARN("OOM");
Expand Down
19 changes: 12 additions & 7 deletions src/MicroOcpp/Core/Time.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -82,14 +82,19 @@ bool Timestamp::setTime(const char *jsonDateString) {
//optional fractals
int ms = 0;
if (jsonDateString[19] == '.') {
if (isdigit(jsonDateString[20]) || //1
isdigit(jsonDateString[21]) || //2
isdigit(jsonDateString[22])) {

ms = (jsonDateString[20] - '0') * 100 +
(jsonDateString[21] - '0') * 10 +
(jsonDateString[22] - '0');
// Check and parse each fractional digit individually to prevent OOB reads
if (jsonDateString[20] != '\0' && isdigit(jsonDateString[20])) {
ms = (jsonDateString[20] - '0') * 100;

if (jsonDateString[21] != '\0' && isdigit(jsonDateString[21])) {
ms += (jsonDateString[21] - '0') * 10;

if (jsonDateString[22] != '\0' && isdigit(jsonDateString[22])) {
ms += (jsonDateString[22] - '0');
}
}
} else {
// Decimal point present but no valid fractional digits
return false;
}
}
Expand Down
5 changes: 5 additions & 0 deletions src/MicroOcpp/Model/Certificates/CertificateMbedTLS.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,11 @@ bool ocpp_get_cert_hash(mbedtls_x509_crt& cacert, HashAlgorithmType hashAlg, ocp
return false;
}

if (cacert.serial.len == 0) {
MO_DBG_ERR("invalid certificate: zero-length serial number");
return false;
}

size_t serial_begin = 0; //trunicate leftmost 0x00 bytes
for (; serial_begin < cacert.serial.len - 1; serial_begin++) { //keep at least 1 byte, even if 0x00
if (cacert.serial.p[serial_begin] != 0) {
Expand Down
37 changes: 3 additions & 34 deletions src/MicroOcpp/Model/Variables/VariableService.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include <MicroOcpp/Operations/GetBaseReport.h>
#include <MicroOcpp/Operations/NotifyReport.h>
#include <MicroOcpp/Core/Request.h>
#include <MicroOcpp/Core/Configuration.h>

#include <cstring>
#include <cctype>
Expand Down Expand Up @@ -327,40 +328,8 @@ SetVariableStatus VariableService::setVariable(Variable::AttributeType attrType,
bool convertibleBool = true;
bool numBool = false;

int nDigits = 0, nNonDigits = 0, nDots = 0, nSign = 0; //"-1.234" has 4 digits, 0 nonDigits, 1 dot and 1 sign. Don't allow comma as seperator. Don't allow e-expressions (e.g. 1.23e-7)
for (const char *c = value; *c; ++c) {
if (*c >= '0' && *c <= '9') {
//int interpretation
if (nDots == 0) { //only append number if before floating point
nDigits++;
numInt *= 10;
numInt += *c - '0';
}
} else if (*c == '.') {
nDots++;
} else if (c == value && *c == '-') {
nSign++;
} else {
nNonDigits++;
}
}

if (nSign == 1) {
numInt = -numInt;
}

int INT_MAXDIGITS; //plausibility check: this allows a numerical range of (-999,999,999 to 999,999,999), or (-9,999 to 9,999) respectively
if (sizeof(int) >= 4UL)
INT_MAXDIGITS = 9;
else
INT_MAXDIGITS = 4;

if (nNonDigits > 0 || nDigits == 0 || nSign > 1 || nDots > 1) {
convertibleInt = false;
}

if (nDigits > INT_MAXDIGITS) {
MO_DBG_DEBUG("Possible integer overflow: key = %s, value = %s", variableName, value);
// Use safe string-to-int conversion with overflow protection
if (!safeStringToInt(value, &numInt)) {
convertibleInt = false;
}

Expand Down
36 changes: 2 additions & 34 deletions src/MicroOcpp/Operations/ChangeConfiguration.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -60,40 +60,8 @@ void ChangeConfiguration::processReq(JsonObject payload) {
bool convertibleBool = true;
bool numBool = false;

int nDigits = 0, nNonDigits = 0, nDots = 0, nSign = 0; //"-1.234" has 4 digits, 0 nonDigits, 1 dot and 1 sign. Don't allow comma as seperator. Don't allow e-expressions (e.g. 1.23e-7)
for (const char *c = value; *c; ++c) {
if (*c >= '0' && *c <= '9') {
//int interpretation
if (nDots == 0) { //only append number if before floating point
nDigits++;
numInt *= 10;
numInt += *c - '0';
}
} else if (*c == '.') {
nDots++;
} else if (c == value && *c == '-') {
nSign++;
} else {
nNonDigits++;
}
}

if (nSign == 1) {
numInt = -numInt;
}

int INT_MAXDIGITS; //plausibility check: this allows a numerical range of (-999,999,999 to 999,999,999), or (-9,999 to 9,999) respectively
if (sizeof(int) >= 4UL)
INT_MAXDIGITS = 9;
else
INT_MAXDIGITS = 4;

if (nNonDigits > 0 || nDigits == 0 || nSign > 1 || nDots > 1) {
convertibleInt = false;
}

if (nDigits > INT_MAXDIGITS) {
MO_DBG_DEBUG("Possible integer overflow: key = %s, value = %s", key, value);
// Use safe string-to-int conversion with overflow protection
if (!safeStringToInt(value, &numInt)) {
convertibleInt = false;
}

Expand Down
Loading