diff --git a/source/bulkdata/reportprofiles.c b/source/bulkdata/reportprofiles.c index 080b4197..3a0a12f0 100644 --- a/source/bulkdata/reportprofiles.c +++ b/source/bulkdata/reportprofiles.c @@ -30,6 +30,7 @@ #include #include #include +#include #include "reportprofiles.h" #include "xconfclient.h" @@ -50,6 +51,7 @@ #include "t2parser.h" #include "telemetry2_0.h" #include "t2MtlsUtils.h" +#include "t2common.h" #include "persistence.h" #ifdef LIBRDKCERTSEL_BUILD #include "curlinterface.h" @@ -622,8 +624,14 @@ T2ERROR initReportProfiles() check_component_crash(TELEMETRY_INIT_FILE_BOOTUP); //Touching TELEMETRY_INIT_FILE_BOOTUP during Bootup - system("touch /tmp/telemetry_initialized_bootup"); - T2Debug(" %s Touched \n", TELEMETRY_INIT_FILE_BOOTUP); + if (!createSecureTempFlag(TELEMETRY_INIT_FILE_BOOTUP)) + { + T2Error("Failed to create secure boot flag %s\n", TELEMETRY_INIT_FILE_BOOTUP); + } + else + { + T2Debug(" %s Touched \n", TELEMETRY_INIT_FILE_BOOTUP); + } } #endif //Web Config Framework init ends @@ -642,11 +650,9 @@ T2ERROR initReportProfiles() { unregisterDEforCompEventList(); createComponentDataElements(); - FILE* cfgReadyFlag = NULL ; - cfgReadyFlag = fopen(T2_CONFIG_READY, "w+"); - if(cfgReadyFlag) + if (!createSecureTempFlag(T2_CONFIG_READY)) { - fclose(cfgReadyFlag); + T2Error("Failed to create secure config ready flag %s\n", T2_CONFIG_READY); } setT2EventReceiveState(T2_STATE_CONFIG_READY); T2Info("T2 is now Ready to be configured for report profiles\n"); @@ -658,16 +664,13 @@ T2ERROR initReportProfiles() // This indicates telemetry has started - FILE* bootFlag = NULL ; - bootFlag = fopen(BOOTFLAG, "w+"); - if(bootFlag) + if (!createSecureTempFlag(BOOTFLAG)) { - T2Debug("%s Touched \n", BOOTFLAG); - fclose(bootFlag); + T2Error("Failed to create secure T2 bootflag %s \n", BOOTFLAG); } else { - T2Error("Failed to create T2 bootflag %s \n", BOOTFLAG); + T2Debug("%s Touched \n", BOOTFLAG); } T2Debug("%s --out\n", __FUNCTION__); @@ -932,6 +935,12 @@ void ReportProfiles_ProcessReportProfilesBlob(cJSON *profiles_root, bool rprofil } cJSON* nameObj = cJSON_GetObjectItem(singleProfile, "name"); + if (!cJSON_IsString(nameObj) || !nameObj->valuestring || !nameObj->valuestring[0] || + strspn(nameObj->valuestring, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-") != strlen(nameObj->valuestring)) + { + T2Error("Invalid profile name in JSON configuration\n"); + continue; + } cJSON* hashObj = cJSON_GetObjectItem(singleProfile, "hash"); if(hashObj == NULL) { @@ -946,7 +955,18 @@ void ReportProfiles_ProcessReportProfilesBlob(cJSON *profiles_root, bool rprofil } ReportProfile *profileEntry = (ReportProfile *)malloc(sizeof(ReportProfile)); + if (profileEntry == NULL) + { + T2Error("Failed to allocate report profile entry\n"); + continue; + } profileName = strdup(nameObj->valuestring); + if (profileName == NULL) + { + T2Error("Failed to copy profile name from JSON configuration\n"); + free(profileEntry); + continue; + } profileEntry->hash = strdup(hashObj->valuestring); profileEntry->config = cJSON_PrintUnformatted(profileObj); hash_map_put(receivedProfileHashMap, profileName, profileEntry, freeReportProfileHashMap); @@ -1400,6 +1420,29 @@ int __ReportProfiles_ProcessReportProfilesMsgPackBlob(void *msgpack, bool checkP continue; } msgpack_object* nameObj = msgpack_get_map_value(singleProfile, "name"); + size_t nameIndex; + if (nameObj == NULL || nameObj->type != MSGPACK_OBJECT_STR || + nameObj->via.str.ptr == NULL || nameObj->via.str.size == 0) + { + T2Error("Invalid profile name in MsgPack configuration\n"); + continue; + } + for (nameIndex = 0; nameIndex < nameObj->via.str.size; nameIndex++) + { + unsigned char character = (unsigned char)nameObj->via.str.ptr[nameIndex]; + if (!((character >= 'a' && character <= 'z') || + (character >= 'A' && character <= 'Z') || + (character >= '0' && character <= '9') || + character == '_' || character == '-')) + { + T2Error("Invalid profile name in MsgPack configuration\n"); + break; + } + } + if (nameIndex != nameObj->via.str.size) + { + continue; + } msgpack_object* hashObj = msgpack_get_map_value(singleProfile, "hash"); if (hashObj == NULL) { @@ -1417,6 +1460,11 @@ int __ReportProfiles_ProcessReportProfilesMsgPackBlob(void *msgpack, bool checkP char *existingProfileHash = NULL; Profile *profile = NULL; profileName = msgpack_strdup(nameObj); + if (profileName == NULL) + { + T2Error("Failed to copy profile name from MsgPack configuration\n"); + continue; + } existingProfileHash = hash_map_remove(profileHashMap, profileName); if(NULL == existingProfileHash) { diff --git a/source/bulkdata/t2eventreceiver.c b/source/bulkdata/t2eventreceiver.c index 426dfc2a..3c51610e 100644 --- a/source/bulkdata/t2eventreceiver.c +++ b/source/bulkdata/t2eventreceiver.c @@ -19,9 +19,14 @@ #include #include +#include +#include +#include +#include #include "t2eventreceiver.h" +#include "t2common.h" #include "t2collection.h" #include "t2markers.h" #include "telemetry2_0.h" @@ -32,6 +37,8 @@ #define T2EVENTQUEUE_MAX_LIMIT 200 #define MESSAGE_DELIMITER "<#=#>" +#define MAX_EVENT_NAME_LEN 256 +#define MAX_EVENT_VALUE_LEN 4096 static queue_t *eQueue = NULL; static bool EREnabled = false; @@ -80,6 +87,24 @@ static char *strSplit(char *str, char *delim) return ret; } +static bool isValidEventLength(const char *name, const char *value) +{ + if(name == NULL || value == NULL) + { + return false; + } + + size_t nameLen = strlen(name); + size_t valueLen = strlen(value); + if(nameLen == 0 || nameLen > MAX_EVENT_NAME_LEN || valueLen > MAX_EVENT_VALUE_LEN) + { + T2Warning("Dropping oversized telemetry event: nameLen=%zu valueLen=%zu\n", nameLen, valueLen); + return false; + } + + return true; +} + void freeT2Event(void *data) { if(data != NULL) @@ -114,51 +139,59 @@ void T2ER_PushDataWithDelim(char* eventInfo, char* user_data) char* token = strSplit(eventInfo, MESSAGE_DELIMITER); if(token != NULL) { - T2Event *event = (T2Event *) malloc(sizeof(T2Event)); - if(event) + char* valueToken = strSplit(NULL, MESSAGE_DELIMITER); + if(valueToken != NULL) { - event->name = strdup(token); - token = strSplit(NULL, MESSAGE_DELIMITER); - if(token != NULL) + if(!isValidEventLength(token, valueToken)) { - event->value = strdup(token); - if(t2_queue_count(eQueue) > T2EVENTQUEUE_MAX_LIMIT) - { - T2Warning("T2EventQueue max limit : %d reached, dropping packet for eventName : %s eventValue : %s\n", - T2EVENTQUEUE_MAX_LIMIT, event->name, event->value); - free(event->value); - free(event->name); - free(event); - } - else + T2Warning("Dropping event with invalid length for eventName : %s eventValue : %s\n", token, valueToken); + } + else if(t2_queue_count(eQueue) > T2EVENTQUEUE_MAX_LIMIT) + { + T2Warning("T2EventQueue max limit : %d reached, dropping packet for eventName : %s eventValue : %s\n", + T2EVENTQUEUE_MAX_LIMIT, token, valueToken); + } + else + { + T2Event *event = (T2Event *) malloc(sizeof(T2Event)); + if(event != NULL) { - T2Debug("Adding eventName : %s eventValue : %s to t2event queue\n", event->name, event->value); - t2_queue_push(eQueue, (void *) event); - // Check stopDispatchThread with proper lock to avoid stale reads from compiler - // optimizations or CPU caching. We hold sTDMutex while reading stopDispatchThread - // to ensure proper synchronization with the thread that sets this flag. - if(pthread_mutex_lock(&sTDMutex) == 0) + event->name = strdup(token); + event->value = strdup(valueToken); + if(event->name != NULL && event->value != NULL) { - bool shouldSignal = !stopDispatchThread; - pthread_mutex_unlock(&sTDMutex); - if(shouldSignal) + T2Debug("Adding eventName : %s eventValue : %s to t2event queue\n", event->name, event->value); + t2_queue_push(eQueue, (void *) event); + // Check stopDispatchThread with proper lock to avoid stale reads from compiler + // optimizations or CPU caching. We hold sTDMutex while reading stopDispatchThread + // to ensure proper synchronization with the thread that sets this flag. + if(pthread_mutex_lock(&sTDMutex) == 0) { - ret = pthread_cond_signal(&erCond); - if(ret != 0) // pthread cond signal failed so return after unlocking the mutex + bool shouldSignal = !stopDispatchThread; + pthread_mutex_unlock(&sTDMutex); + if(shouldSignal) { - T2Error("%s pthread_cond_signal for erCond failed with error code : %d\n", __FUNCTION__, ret); + ret = pthread_cond_signal(&erCond); + if(ret != 0) // pthread cond signal failed so return after unlocking the mutex + { + T2Error("%s pthread_cond_signal for erCond failed with error code : %d\n", __FUNCTION__, ret); + } } } - + } + else + { + free(event->name); + free(event->value); + free(event); + T2Error("Failed to duplicate event memory\n"); } } } - else - { - free(event->name); - free(event); - T2Error("Missing event value\n"); - } + } + else + { + T2Error("Missing event value\n"); } } else @@ -197,7 +230,11 @@ void T2ER_Push(char* eventName, char* eventValue) T2Error("%s pthread_mutex_lock for erMutex failed\n", __FUNCTION__); return; } - if(t2_queue_count(eQueue) > T2EVENTQUEUE_MAX_LIMIT) + if(!isValidEventLength(eventName, eventValue)) + { + T2Warning("Dropping event with invalid length for eventName : %s eventValue : %s\n", eventName, eventValue); + } + else if(t2_queue_count(eQueue) > T2EVENTQUEUE_MAX_LIMIT) { T2Warning("T2EventQueue max limit : %d reached, dropping packet for eventName : %s eventValue : %s\n", T2EVENTQUEUE_MAX_LIMIT, eventName, eventValue); @@ -210,9 +247,18 @@ void T2ER_Push(char* eventName, char* eventValue) { event->name = strdup(eventName); event->value = strdup(eventValue); - T2Debug("Adding eventName : %s eventValue : %s to t2event queue\n", event->name, event->value); - t2_queue_push(eQueue, (void *) event); - + if(event->name != NULL && event->value != NULL) + { + T2Debug("Adding eventName : %s eventValue : %s to t2event queue\n", event->name, event->value); + t2_queue_push(eQueue, (void *) event); + } + else + { + free(event->name); + free(event->value); + free(event); + T2Error("Failed to duplicate event memory\n"); + } } } @@ -404,7 +450,7 @@ T2ERROR T2ER_Init() registerForTelemetryEvents(T2ER_PushDataWithDelim); } - if (system("touch /tmp/.t2ReadyToReceiveEvents") != 0) + if (!createSecureTempFlag("/tmp/.t2ReadyToReceiveEvents")) { T2Error("Failed to create /tmp/.t2ReadyToReceiveEvents flag file\n"); } diff --git a/source/ccspinterface/rbusInterface.c b/source/ccspinterface/rbusInterface.c index 2fd73fdd..15530bf7 100644 --- a/source/ccspinterface/rbusInterface.c +++ b/source/ccspinterface/rbusInterface.c @@ -1038,6 +1038,10 @@ static rbusError_t dcmOnDemandMethodHandler(rbusHandle_t handle, char const* met if(pthread_create(&rpOnDemandTh, &rpOnDemandAttr, reportOnDemandCallBack, ON_DEMAND_ACTION_UPLOAD) != 0) { T2Error("Failed to create thread for report on demand.\n"); + onDemandReportCallBackHandler = NULL; + pthread_mutex_unlock(&asyncMutex); + pthread_attr_destroy(&rpOnDemandAttr); + return RBUS_ERROR_BUS_ERROR; } pthread_attr_destroy(&rpOnDemandAttr); @@ -1079,20 +1083,9 @@ static rbusError_t dcmOnDemandMethodHandler(rbusHandle_t handle, char const* met } else if(!strncmp(methodName, T2_ABORT_ON_DEMAND_REPORT, maxParamLen)) { - T2Info("%s Aborting previous ondemand report upload %s \n", __FUNCTION__, methodName); + T2Info("%s Abort is no longer supported; request is accepted without creating a detached worker thread to avoid thread-flood DoS\n", __FUNCTION__); - pthread_t rpOnDemandTh; - pthread_attr_t rpOnDemandAttr; - pthread_attr_init(&rpOnDemandAttr); - pthread_attr_setdetachstate(&rpOnDemandAttr, PTHREAD_CREATE_DETACHED); - // Calls within reportOnDemandCallBack are thread safe, thread synchronization is not required - if(pthread_create(&rpOnDemandTh, &rpOnDemandAttr, reportOnDemandCallBack, ON_DEMAND_ACTION_ABORT) != 0) - { - T2Error("Failed to create thread for report on demand.\n"); - } - - pthread_attr_destroy(&rpOnDemandAttr); if(NULL != asyncCallBackHandle) { rbusObject_t outputParams; diff --git a/source/dcautil/dca.c b/source/dcautil/dca.c index 19144694..758930c7 100644 --- a/source/dcautil/dca.c +++ b/source/dcautil/dca.c @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -896,6 +897,103 @@ static int processPatternWithOptimizedFunction(GrepMarker* marker, FileDescripto return 0; } +static bool isSimpleLogFilename(const char* logFile) +{ + if (!logFile || *logFile == '\0') + { + return false; + } + + if (logFile[0] == '/' || strchr(logFile, '/') != NULL) + { + return false; + } + + if (strstr(logFile, "..") != NULL) + { + return false; + } + + return true; +} + +static bool resolveApprovedLogPath(const char* logPath, const char* logFile, char* out_path, size_t out_size) +{ + if (!logFile || *logFile == '\0' || !out_path || out_size == 0) + { + return false; + } + + if (isSimpleLogFilename(logFile)) + { + if (!logPath || *logPath == '\0') + { + T2Debug("Rejected log file with no valid base path: %s\n", logFile); + return false; + } + + snprintf(out_path, out_size, "%s/%s", logPath, logFile); + return true; + } + + if (logFile[0] == '/') + { + snprintf(out_path, out_size, "%s", logFile); + } + else if (logPath && *logPath != '\0') + { + snprintf(out_path, out_size, "%s/%s", logPath, logFile); + } + else + { + T2Debug("Rejected log file with no valid base path: %s\n", logFile); + return false; + } + + char resolved[PATH_MAX]; + if (realpath(out_path, resolved) == NULL) + { + T2Debug("Rejected unsafe or unresolved log path %s: %s\n", out_path, strerror(errno)); + return false; + } + + const char* allowed_roots[] = + { + LOGPATH, + PREVIOUS_LOGS_PATH, + "/opt/logs", + "/var/log/messages", + NULL + }; + + for (int i = 0; allowed_roots[i] != NULL; ++i) + { + const char* root = allowed_roots[i]; + if (!root || *root == '\0') + { + continue; + } + + char root_path[PATH_MAX]; + snprintf(root_path, sizeof(root_path), "%s", root); + size_t root_len = strlen(root_path); + while (root_len > 1 && root_path[root_len - 1] == '/') + { + root_path[--root_len] = '\0'; + } + + if (strncmp(resolved, root_path, root_len) == 0 && + (resolved[root_len] == '\0' || resolved[root_len] == '/')) + { + snprintf(out_path, out_size, "%s", resolved); + return true; + } + } + + T2Debug("Rejected log path outside approved roots: %s -> %s\n", out_path, resolved); + return false; +} + static int getLogFileDescriptor(GrepSeekProfile* gsProfile, const char* logPath, const char* logFile, int old_fd, off_t* out_seek_value, bool* out_inode_changed, ino_t* out_stored_inode, bool isCustomPath) { long seek_value_from_map = 0; @@ -907,19 +1005,15 @@ static int getLogFileDescriptor(GrepSeekProfile* gsProfile, const char* logPath, } char logFilePath[PATH_MAX]; - if(logFile[0] == '/') - { - // If the logFile is an absolute path, use it directly - T2Info("Log file is an absolute path not prefix in the directory: %s\n", logFile); - snprintf(logFilePath, sizeof(logFilePath), "%s", logFile); - } - else + if (!resolveApprovedLogPath(logPath, logFile, logFilePath, sizeof(logFilePath))) { - snprintf(logFilePath, sizeof(logFilePath), "%s/%s", logPath, logFile); + T2Error("Refusing to open untrusted log path: %s\n", logFile); + return -1; } + T2Info("Log file resolved to approved path: %s\n", logFilePath); T2Debug("Opening log file %s\n", logFilePath); - int fd = open(logFilePath, O_RDONLY); + int fd = open(logFilePath, O_RDONLY ); if (fd == -1) { T2Debug("Failed to open log file %s\n", logFilePath); @@ -927,9 +1021,9 @@ static int getLogFileDescriptor(GrepSeekProfile* gsProfile, const char* logPath, } struct stat sb; - if (fstat(fd, &sb) == -1) + if (fstat(fd, &sb) == -1 || !S_ISREG(sb.st_mode)) { - T2Debug("Error getting file size for %s\n", logFile); + T2Debug("Error getting file size or invalid file type for %s\n", logFile); close(fd); return -1; } @@ -978,22 +1072,25 @@ static int getLogFileDescriptor(GrepSeekProfile* gsProfile, const char* logPath, static int getRotatedLogFileDescriptor(const char* logPath, const char* logFile) { char logFilePath[PATH_MAX]; - snprintf(logFilePath, sizeof(logFilePath), "%s/%s", logPath, logFile); + if (!resolveApprovedLogPath(logPath, logFile, logFilePath, sizeof(logFilePath))) + { + T2Error("Refusing to open untrusted rotated log path: %s\n", logFile); + return -1; + } + //get the rotated filename char *fileExtn = ".1"; char rotatedlogFilePath[PATH_MAX]; size_t name_len = strlen(logFilePath); if(logFile[0] == '/') { - // If the logFile is an absolute path, use it directly - T2Debug("RotatedLog file is an absolute path not prefix in the directory: %s\n", logFile); - snprintf(rotatedlogFilePath, sizeof(logFilePath), "%s", logFile); + T2Debug("RotatedLog file is an absolute path within an approved root: %s\n", logFile); + snprintf(rotatedlogFilePath, sizeof(rotatedlogFilePath), "%s", logFilePath); } else { - // If the logFile is not an absolute path, prefix it with the logPath T2Debug("RotatedLog file is not an absolute path, prefixing with directory: %s\n", logPath); - snprintf(rotatedlogFilePath, sizeof(rotatedlogFilePath), "%s/%s", logPath, logFile); + snprintf(rotatedlogFilePath, sizeof(rotatedlogFilePath), "%s", logFilePath); } if(name_len > 2 && logFilePath[name_len - 2] == '.' && logFilePath[name_len - 1] == '0') { @@ -1014,9 +1111,9 @@ static int getRotatedLogFileDescriptor(const char* logPath, const char* logFile) } struct stat rb; - if (fstat(rd, &rb) == -1) + if (fstat(rd, &rb) == -1 || !S_ISREG(rb.st_mode)) { - T2Debug("Error getting file size for %s\n", rotatedlogFilePath); + T2Debug("Error getting file size or invalid file type for %s\n", rotatedlogFilePath); close(rd); return -1; } diff --git a/source/dcautil/legacyutils.c b/source/dcautil/legacyutils.c index 083d472a..24ccda86 100644 --- a/source/dcautil/legacyutils.c +++ b/source/dcautil/legacyutils.c @@ -22,6 +22,9 @@ #include #include #include +#include +#include +#include #include "t2log_wrapper.h" #include "legacyutils.h" @@ -34,6 +37,31 @@ static char* PERSISTENT_PATH = NULL; static char* LOG_PATH = NULL; static char* DEVICE_TYPE = NULL; +static bool isValidLogBasePath(const char *path) +{ + if (path == NULL || path[0] != '/') + { + return false; + } + + /* Prevent traversal and Windows-style separators */ + if (strstr(path, "..") != NULL || strchr(path, '\\') != NULL) + { + return false; + } + + /* Reject control characters */ + for (const char *p = path; *p != '\0'; ++p) + { + if (iscntrl((unsigned char)*p)) + { + return false; + } + } + + return true; +} + /** * Start of functions dealing with log seek values */ @@ -224,11 +252,18 @@ void updateIncludeConfVal(char **logpath, char **perspath) if(0 == strncmp(props, "LOG_PATH=", strlen("LOG_PATH="))) { property = property + strlen("LOG_PATH="); - if(LOG_PATH != NULL) + if (isValidLogBasePath(property)) + { + if(LOG_PATH != NULL) + { + free(LOG_PATH); + } + LOG_PATH = strdup(property); + } + else { - free(LOG_PATH); + T2Error("Ignoring invalid LOG_PATH value from include.properties\n"); } - LOG_PATH = strdup(property); } } } diff --git a/source/protocol/http/multicurlinterface.c b/source/protocol/http/multicurlinterface.c index 47baa89c..57874844 100644 --- a/source/protocol/http/multicurlinterface.c +++ b/source/protocol/http/multicurlinterface.c @@ -41,6 +41,7 @@ #include "t2log_wrapper.h" #include "reportprofiles.h" #include "t2MtlsUtils.h" +#include "t2common.h" #ifdef LIBRDKCERTSEL_BUILD #include "rdkcertselector.h" #define FILESCHEME "file://" @@ -626,7 +627,6 @@ static void configure_wan_interface(CURL *easy) } #endif - T2ERROR http_pool_get(const char *url, char **response_data, bool enable_file_output) { T2Debug("%s ++in\n", __FUNCTION__); @@ -844,7 +844,7 @@ T2ERROR http_pool_get(const char *url, char **response_data, bool enable_file_ou // Handle file output for GET requests if (enable_file_output) { - int fd = open(HTTP_RESPONSE_FILE, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR); + int fd = openSecureTempFile(HTTP_RESPONSE_FILE, true); if (fd >= 0) { FILE *httpOutput = fdopen(fd, "w"); @@ -999,7 +999,7 @@ T2ERROR http_pool_post(const char *url, const char *payload) // For now, we will set up file output for POST requests to ensure stability. // Set up file output for POST requests // If file opens successfully, override the default stdout with the file pointer - int curl_output_fd = open("/tmp/curlOutput.txt", O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR); + int curl_output_fd = openSecureTempFile("/tmp/curlOutput.txt", true); FILE *fp = NULL; if (curl_output_fd >= 0) { @@ -1037,7 +1037,7 @@ T2ERROR http_pool_post(const char *url, const char *payload) bool state_red_enable = false; #if defined(ENABLE_RED_RECOVERY_SUPPORT) - state_red_enable = (access("/tmp/stateRedEnabled", F_OK) == 0); + state_red_enable = isStateRedEnabled(); T2Info("%s: state_red_enable: %d\n", __func__, state_red_enable); #endif diff --git a/source/reportgen/reportgen.c b/source/reportgen/reportgen.c index 98834b1f..de8354a7 100644 --- a/source/reportgen/reportgen.c +++ b/source/reportgen/reportgen.c @@ -249,7 +249,14 @@ static T2ERROR applyRegexToValue(char** inputValue, const char* regexPattern) { T2Debug("regexec successful, Match is found %.*s\n", pmatch[0].rm_eo - pmatch[0].rm_so, &(*inputValue)[pmatch[0].rm_so]); - sprintf(string, "%.*s", pmatch[0].rm_eo - pmatch[0].rm_so, &(*inputValue)[pmatch[0].rm_so]); + // Bounds check: ensure match length doesn't exceed buffer + int matchLen = pmatch[0].rm_eo - pmatch[0].rm_so; + if (matchLen >= (int)sizeof(string)) + { + T2Warning("Regex match too long: %d bytes, truncating to %zu\n", matchLen, sizeof(string) - 1); + matchLen = sizeof(string) - 1; + } + snprintf(string, sizeof(string), "%.*s", matchLen, &(*inputValue)[pmatch[0].rm_so]); free(*inputValue); *inputValue = strdup(string); if (*inputValue == NULL) @@ -1031,7 +1038,14 @@ T2ERROR encodeGrepResultInJSON(cJSON *valArray, Vector *grepMarkerList) else { T2Debug("regexec successful, Match is found %.*s\n", pmatch[0].rm_eo - pmatch[0].rm_so, &stringValue[pmatch[0].rm_so]); - sprintf(string[i], "%.*s", pmatch[0].rm_eo - pmatch[0].rm_so, &stringValue[pmatch[0].rm_so]); + // Bounds check: ensure match length doesn't exceed buffer + int matchLen = pmatch[0].rm_eo - pmatch[0].rm_so; + if (matchLen >= (int)sizeof(string[0])) + { + T2Warning("Regex match too long: %d bytes, truncating to %zu\n", matchLen, sizeof(string[0]) - 1); + matchLen = sizeof(string[0]) - 1; + } + snprintf(string[i], sizeof(string[i]), "%.*s", matchLen, &stringValue[pmatch[0].rm_so]); } Vector_PushBack(regaccumulateValues, string[i]); } @@ -1402,7 +1416,14 @@ T2ERROR encodeEventMarkersInJSON(cJSON *valArray, Vector *eventMarkerList) else { T2Debug("regexec successful, Match is found %.*s\n", pmatch[0].rm_eo - pmatch[0].rm_so, &stringValue[pmatch[0].rm_so]); - sprintf(string, "%.*s", pmatch[0].rm_eo - pmatch[0].rm_so, &stringValue[pmatch[0].rm_so]); + // Bounds check: ensure match length doesn't exceed buffer + int matchLen = pmatch[0].rm_eo - pmatch[0].rm_so; + if (matchLen >= (int)sizeof(string)) + { + T2Warning("Regex match too long: %d bytes, truncating to %zu\n", matchLen, sizeof(string) - 1); + matchLen = sizeof(string) - 1; + } + snprintf(string, sizeof(string), "%.*s", matchLen, &stringValue[pmatch[0].rm_so]); strncpy(stringValue, string, strlen(string) + 1); } regfree(®pattern); diff --git a/source/t2parser/t2parser.c b/source/t2parser/t2parser.c index 0a42d264..e909049e 100644 --- a/source/t2parser/t2parser.c +++ b/source/t2parser/t2parser.c @@ -1659,6 +1659,13 @@ T2ERROR processConfiguration(char** configData, char *profileName, char* profile cJSON_Delete(json_root); return T2ERROR_FAILURE; } + if (!profileName || !profileName[0] || + strspn(profileName, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-") != strlen(profileName)) + { + T2Error("ERROR: Profile name vulnerability, security threat on profile name \n"); + cJSON_Delete(json_root); + return T2ERROR_FAILURE; + } if(jprofileReportingInterval && jprofilemaxUploadLatency && ((jprofileReportingInterval->valueint * 1000) < jprofilemaxUploadLatency->valueint)) { T2Error("MaxUploadLatency is greater than reporting interval. Invalid Profile\n"); @@ -1859,10 +1866,18 @@ T2ERROR processConfiguration(char** configData, char *profileName, char* profile if((strcmp(jprofileProtocol->valuestring, "HTTP") == 0)) { profile->t2HTTPDest = (T2HTTP *) malloc(sizeof(T2HTTP)); + if(profile->t2HTTPDest) + { + memset(profile->t2HTTPDest, 0, sizeof(*profile->t2HTTPDest)); + } } else if ((strcmp(jprofileProtocol->valuestring, "RBUS_METHOD") == 0)) { profile->t2RBUSDest = malloc(sizeof(T2RBUS)); + if(profile->t2RBUSDest) + { + memset(profile->t2RBUSDest, 0, sizeof(*profile->t2RBUSDest)); + } } if ( (profile->t2HTTPDest == NULL) && (profile->t2RBUSDest == NULL) ) @@ -3150,6 +3165,7 @@ T2ERROR processMsgPackConfiguration(msgpack_object *profiles_array_map, Profile msgpack_object *FirstReportInterval_u64 = NULL; msgpack_object *MaxUploadLatency_u64 = NULL; msgpack_object *RbusMethod_map; + size_t nameIndex; int ret = 0; T2Debug("%s --in\n", __FUNCTION__); @@ -3163,8 +3179,34 @@ T2ERROR processMsgPackConfiguration(msgpack_object *profiles_array_map, Profile name_str = msgpack_get_map_value(profiles_array_map, "name"); + if (name_str == NULL || name_str->type != MSGPACK_OBJECT_STR || + name_str->via.str.ptr == NULL || name_str->via.str.size == 0) + { + T2Error("Invalid profile name in MsgPack configuration\n"); + free(profile); + return T2ERROR_FAILURE; + } + for (nameIndex = 0; nameIndex < name_str->via.str.size; nameIndex++) + { + unsigned char character = (unsigned char)name_str->via.str.ptr[nameIndex]; + if (!((character >= 'a' && character <= 'z') || + (character >= 'A' && character <= 'Z') || + (character >= '0' && character <= '9') || + character == '_' || character == '-')) + { + T2Error("Invalid profile name in MsgPack configuration\n"); + free(profile); + return T2ERROR_FAILURE; + } + } msgpack_print(name_str, msgpack_get_obj_name(name_str)); profile->name = msgpack_strdup(name_str); + if (profile->name == NULL) + { + T2Error("Failed to copy profile name from MsgPack configuration\n"); + free(profile); + return T2ERROR_MEMALLOC_FAILED; + } hash_str = msgpack_get_map_value(profiles_array_map, "hash"); if(NULL == hash_str) @@ -3406,6 +3448,7 @@ T2ERROR processMsgPackConfiguration(msgpack_object *profiles_array_map, Profile T2Error("Malloc error exiting: can not allocate memory to create profile->t2HTTPDest \n"); return T2ERROR_MEMALLOC_FAILED; } + memset(profile->t2HTTPDest, 0, sizeof(*profile->t2HTTPDest)); msgpack_object *HTTP_map = msgpack_get_map_value(value_map, "HTTP"); msgpack_object *URL_str = msgpack_get_map_value(HTTP_map, "URL"); msgpack_object *Compression_str = msgpack_get_map_value(HTTP_map, "Compression"); @@ -3444,6 +3487,7 @@ T2ERROR processMsgPackConfiguration(msgpack_object *profiles_array_map, Profile T2Error("Malloc error exiting: can not allocate memory to create profile->t2RBUSDest \n"); return T2ERROR_MEMALLOC_FAILED; } + memset(profile->t2RBUSDest, 0, sizeof(*profile->t2RBUSDest)); RbusMethod_map = msgpack_get_map_value(value_map, "RBUS_METHOD"); RbusMethod_str = msgpack_get_map_value(RbusMethod_map, "Method"); msgpack_print(RbusMethod_str, msgpack_get_obj_name(RbusMethod_str)); diff --git a/source/t2parser/t2parserxconf.c b/source/t2parser/t2parserxconf.c index 8e39cbe5..0a7c7339 100644 --- a/source/t2parser/t2parserxconf.c +++ b/source/t2parser/t2parserxconf.c @@ -22,6 +22,9 @@ #include #include #include +#include +#include +#include #include "t2parserxconf.h" #include "xconfclient.h" @@ -45,6 +48,198 @@ #define ACCUMULATE_MARKER_SUFFIX "_accum" #define MAX_PARAM_LEN 15 +static bool isLocalNumericAddress(const char *host) +{ + if (host == NULL || *host == '\0') + { + return false; + } + + struct in_addr addr4; + if (inet_pton(AF_INET, host, &addr4) == 1) + { + uint32_t ip = ntohl(addr4.s_addr); + return ((ip & 0xFF000000u) == 0x7F000000u) || (ip == 0x00000000u); + } + + if (inet_aton(host, &addr4) == 1) + { + uint32_t ip = ntohl(addr4.s_addr); + return ((ip & 0xFF000000u) == 0x7F000000u) || (ip == 0x00000000u); + } + + if (strchr(host, ':') == NULL) + { + errno = 0; + char *end = NULL; + unsigned long long numeric = strtoull(host, &end, 0); + if (errno == 0 && end != host && *end == '\0' && numeric <= 0xFFFFFFFFULL) + { + struct in_addr parsed; + parsed.s_addr = htonl((uint32_t)numeric); + uint32_t ip = ntohl(parsed.s_addr); + return ((ip & 0xFF000000u) == 0x7F000000u) || (ip == 0x00000000u); + } + } + + struct in6_addr addr6; + if (inet_pton(AF_INET6, host, &addr6) == 1) + { + if (IN6_IS_ADDR_LOOPBACK(&addr6) || IN6_IS_ADDR_UNSPECIFIED(&addr6)) + { + return true; + } + + if (IN6_IS_ADDR_V4MAPPED(&addr6)) + { + struct in_addr mapped_v4; + memcpy(&mapped_v4, &addr6.s6_addr[12], sizeof(mapped_v4)); + uint32_t mapped_ip = ntohl(mapped_v4.s_addr); + return ((mapped_ip & 0xFF000000u) == 0x7F000000u) || (mapped_ip == 0x00000000u); + } + } + + return false; +} + +static bool isValidHttpsUploadUrl(const char *url) +{ + if (url == NULL || *url == '\0') + { + return false; + } + + char url_copy[2048]; + size_t url_len = strlen(url); + if (url_len == 0 || url_len >= sizeof(url_copy)) + { + return false; + } + + snprintf(url_copy, sizeof(url_copy), "%s", url); + char *scheme_end = strstr(url_copy, "://"); + if (scheme_end == NULL) + { + return false; + } + + *scheme_end = '\0'; + if (strcasecmp(url_copy, "https") != 0) + { + return false; + } + + char *authority = scheme_end + 3; + if (*authority == '\0') + { + return false; + } + + if (strchr(authority, '@') != NULL) + { + return false; + } + + char *host_end = strpbrk(authority, "/?#"); + if (host_end != NULL) + { + *host_end = '\0'; + } + + char host[256]; + memset(host, 0, sizeof(host)); + if (authority[0] == '[') + { + char *close_bracket = strchr(authority, ']'); + if (close_bracket == NULL) + { + return false; + } + size_t host_len = (size_t)(close_bracket - authority - 1); + if (host_len == 0 || host_len >= sizeof(host)) + { + return false; + } + memcpy(host, authority + 1, host_len); + host[host_len] = '\0'; + } + else + { + char *port_sep = strrchr(authority, ':'); + if (port_sep != NULL) + { + char *first_colon = strchr(authority, ':'); + if (first_colon != NULL && first_colon != port_sep) + { + return false; + } + + size_t host_len = (size_t)(port_sep - authority); + if (host_len == 0 || host_len >= sizeof(host)) + { + return false; + } + memcpy(host, authority, host_len); + host[host_len] = '\0'; + + char *port_value = port_sep + 1; + if (*port_value == '\0') + { + return false; + } + for (char *p = port_value; *p != '\0'; ++p) + { + if (*p < '0' || *p > '9') + { + return false; + } + } + } + else + { + size_t hostname_len = strlen(authority); + if (hostname_len == 0 || hostname_len >= sizeof(host)) + { + return false; + } + snprintf(host, sizeof(host), "%s", authority); + } + } + + if (host[0] == '\0') + { + return false; + } + + if (strchr(host, '%') != NULL || isLocalNumericAddress(host)) + { + return false; + } + + char lower_host[256]; + size_t host_len = strlen(host); + if (host_len >= sizeof(lower_host)) + { + return false; + } + for (size_t i = 0; i < host_len; ++i) + { + lower_host[i] = (char)tolower((unsigned char)host[i]); + } + lower_host[host_len] = '\0'; + + if (strcmp(lower_host, "localhost") == 0 || + strcmp(lower_host, "localhost.localdomain") == 0 || + (host_len >= 10 && strncmp(lower_host, "localhost.", 10) == 0) || + (host_len >= 6 && strcmp(lower_host + host_len - 6, ".local") == 0) || + (host_len >= 9 && strcmp(lower_host + host_len - 9, ".internal") == 0)) + { + return false; + } + + return true; +} + static int getScheduleInSeconds(const char* cronPattern) { unsigned int scheduleIntervalInMin = 15; // Default value from field settings @@ -65,8 +260,17 @@ static int getScheduleInSeconds(const char* cronPattern) discardPatternLen = strlen(discardPattern); int timeInMinDataLen = inputMatchSize - discardPatternLen; char scheduleString[10] = { '\0' }; - strncpy(scheduleString, inputMatchPattern, timeInMinDataLen); - scheduleIntervalInMin = atoi(scheduleString); + // Bounds check: ensure the schedule string doesn't exceed buffer + if (timeInMinDataLen >= (int)sizeof(scheduleString)) + { + T2Error("Schedule string too long: %d bytes, max %zu\n", timeInMinDataLen, sizeof(scheduleString) - 1); + } + else + { + strncpy(scheduleString, inputMatchPattern, sizeof(scheduleString) - 1); + scheduleString[sizeof(scheduleString) - 1] = '\0'; + scheduleIntervalInMin = atoi(scheduleString); + } } } @@ -231,6 +435,12 @@ T2ERROR processConfigurationXConf(char* configData, ProfileXConf **localProfile) } // if(jprofileID) // T2Debug("profile id = %s\n", jprofileID->valuestring); + if (!cJSON_IsString(jprofileName) || !jprofileName->valuestring || !jprofileName->valuestring[0] || strspn(jprofileName->valuestring, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-") != strlen(jprofileName->valuestring)) + { + T2Error("ERROR: Profile name vulnerability, security threat on profile name \n"); + cJSON_Delete(json_root); + return T2ERROR_FAILURE; + } if(jprofileName) { T2Debug("profile name = %s\n", jprofileName->valuestring); @@ -252,6 +462,13 @@ T2ERROR processConfigurationXConf(char* configData, ProfileXConf **localProfile) return T2ERROR_FAILURE; } + if (!isValidHttpsUploadUrl(juploadUrl->valuestring)) + { + T2Error("Rejected invalid upload URL in XCONF profile: %s\n", juploadUrl->valuestring ? juploadUrl->valuestring : "(null)"); + cJSON_Delete(json_root); + return T2ERROR_FAILURE; + } + int reportIntervalInSec = getScheduleInSeconds(jschedule->valuestring); T2Info("Received profile name : %s with interval of : %d secs and upload url : %s \n", jprofileName->valuestring, reportIntervalInSec, juploadUrl->valuestring); @@ -267,6 +484,18 @@ T2ERROR processConfigurationXConf(char* configData, ProfileXConf **localProfile) profile->protocol = strdup("HTTP"); profile->t2HTTPDest = (T2HTTP *)malloc(sizeof(T2HTTP)); + if(profile->t2HTTPDest == NULL) + { + T2Error("Malloc error exiting: can not allocate memory to create profile->t2HTTPDest \n"); + free(profile->protocol); + free(profile->name); + pthread_cond_destroy(&profile->reportInProgressCond); + pthread_mutex_destroy(&profile->reportInProgressMutex); + free(profile); + cJSON_Delete(json_root); + return T2ERROR_MEMALLOC_FAILED; + } + memset(profile->t2HTTPDest, 0, sizeof(*profile->t2HTTPDest)); profile->t2HTTPDest->URL = strdup(juploadUrl->valuestring); profile->encodingType = strdup("JSON"); diff --git a/source/test/bulkdata/profilexconfTest.cpp b/source/test/bulkdata/profilexconfTest.cpp index e2ad1068..5f3aed5c 100644 --- a/source/test/bulkdata/profilexconfTest.cpp +++ b/source/test/bulkdata/profilexconfTest.cpp @@ -199,7 +199,7 @@ TEST_F(profileXconfTestFixture, InitandUninit) "]," "\"schedule\":\"*/1 * * * *\"," "\"expires\":0," - "\"telemetryProfile:name\":\"NEW TEST PROFILE\"," + "\"telemetryProfile:name\":\"NEW_TEST_PROFILE\"," "\"uploadRepository:URL\":\"https://mock1xconf:50051/dataLakeMockXconf\"," "\"uploadRepository:uploadProtocol\":\"HTTP\"" "}" diff --git a/source/test/dcautils/dcautilTest.cpp b/source/test/dcautils/dcautilTest.cpp index f54077ab..7992d354 100644 --- a/source/test/dcautils/dcautilTest.cpp +++ b/source/test/dcautils/dcautilTest.cpp @@ -51,6 +51,14 @@ using ::testing::_; using ::testing::Return; using ::testing::StrEq; +static void setRegularFileStat(struct stat* statbuf, off_t size, ino_t inode = 12345) +{ + memset(statbuf, 0, sizeof(*statbuf)); + statbuf->st_mode = S_IFREG | 0644; + statbuf->st_size = size; + statbuf->st_ino = inode; +} + FileMock *g_fileIOMock = NULL; SystemMock * g_systemMock = NULL; rdklogMock *m_rdklogMock = NULL; @@ -1208,11 +1216,11 @@ TEST_F(dcaTestFixture, getDCAResultsInVector_1) EXPECT_CALL(*g_fileIOMock, fstat(_, _)) .Times(2) .WillOnce([](int fd, struct stat* statbuf) { - statbuf->st_size = 1235; // Set file size + setRegularFileStat(statbuf, 1235, 12345); return 0; // Success }) .WillOnce([](int fd, struct stat* statbuf) { - statbuf->st_size = 1300; // Set file size + setRegularFileStat(statbuf, 1300, 12345); return 0; // Success }); @@ -1288,11 +1296,11 @@ TEST_F(dcaTestFixture, getDCAResultsInVector_2) EXPECT_CALL(*g_fileIOMock, fstat(_, _)) .Times(2) .WillOnce([](int fd, struct stat* statbuf) { - statbuf->st_size = 1300; // Set file size + setRegularFileStat(statbuf, 1300, 12345); return 0; // Success }) .WillOnce([](int fd, struct stat* statbuf) { - statbuf->st_size = 1300; // Set file size + setRegularFileStat(statbuf, 1300, 12345); return 0; // Success }); @@ -1375,19 +1383,19 @@ TEST_F(dcaTestFixture, getDCAResultsInVector_3) EXPECT_CALL(*g_fileIOMock, fstat(_, _)) .Times(4) .WillOnce([](int fd, struct stat* statbuf) { - statbuf->st_size = 1235; // Set file size + setRegularFileStat(statbuf, 1235, 12345); return 0; // Success }) .WillOnce([](int fd, struct stat* statbuf) { - statbuf->st_size = 1235; // Set file size + setRegularFileStat(statbuf, 1235, 12345); return 0; // Success }) .WillOnce([](int fd, struct stat* statbuf) { - statbuf->st_size = 1000; // Set file size + setRegularFileStat(statbuf, 1000, 12345); return 0; // Success }) .WillOnce([](int fd, struct stat* statbuf) { - statbuf->st_size = 1000; // Set file size + setRegularFileStat(statbuf, 1000, 12345); return 0; // Success }); @@ -1478,19 +1486,19 @@ TEST_F(dcaTestFixture, getDCAResultsInVector_Accum) EXPECT_CALL(*g_fileIOMock, fstat(_, _)) .Times(testing::AtMost(4)) .WillOnce([](int fd, struct stat* statbuf) { - statbuf->st_size = 1235; // Set file size + setRegularFileStat(statbuf, 1235, 12345); return 0; // Success }) .WillOnce([](int fd, struct stat* statbuf) { - statbuf->st_size = 1235; // Set file size + setRegularFileStat(statbuf, 1235, 12345); return 0; // Success }) .WillOnce([](int fd, struct stat* statbuf) { - statbuf->st_size = 1000; // Set file size + setRegularFileStat(statbuf, 1000, 12345); return 0; // Success }) .WillOnce([](int fd, struct stat* statbuf) { - statbuf->st_size = 1000; // Set file size + setRegularFileStat(statbuf, 1000, 12345); return 0; // Success }); //getDeltainmmapsearch @@ -1588,23 +1596,19 @@ TEST_F(dcaTestFixture, getDCAResultsInVector_InodeChanged_NewFileGrowsPastSeek) EXPECT_CALL(*g_fileIOMock, fstat(_, _)) .Times(4) .WillOnce([](int fd, struct stat* statbuf) { - statbuf->st_size = 8000; - statbuf->st_ino = 22222; // Different inode → rotation detected + setRegularFileStat(statbuf, 8000, 22222); return 0; }) .WillOnce([](int fd, struct stat* statbuf) { - statbuf->st_size = 8000; - statbuf->st_ino = 22222; + setRegularFileStat(statbuf, 8000, 22222); return 0; }) .WillOnce([](int fd, struct stat* statbuf) { - statbuf->st_size = 7000; // Rotated file (old log) > seek_value (5000) - statbuf->st_ino = 11111; // Old inode + setRegularFileStat(statbuf, 7000, 11111); return 0; }) .WillOnce([](int fd, struct stat* statbuf) { - statbuf->st_size = 7000; - statbuf->st_ino = 11111; + setRegularFileStat(statbuf, 7000, 11111); return 0; }); @@ -1707,23 +1711,19 @@ TEST_F(dcaTestFixture, getDCAResultsInVector_MultiRotation_IntermediateFile) EXPECT_CALL(*g_fileIOMock, fstat(_, _)) .Times(4) .WillOnce([](int fd, struct stat* statbuf) { - statbuf->st_size = 8000; - statbuf->st_ino = 22222; // Current file — different from stored + setRegularFileStat(statbuf, 8000, 22222); return 0; }) .WillOnce([](int fd, struct stat* statbuf) { - statbuf->st_size = 8000; - statbuf->st_ino = 22222; + setRegularFileStat(statbuf, 8000, 22222); return 0; }) .WillOnce([](int fd, struct stat* statbuf) { - statbuf->st_size = 3000; // Intermediate rotated file - statbuf->st_ino = 33333; // DIFFERENT from stored 11111 — multi-rotation! + setRegularFileStat(statbuf, 3000, 33333); return 0; }) .WillOnce([](int fd, struct stat* statbuf) { - statbuf->st_size = 3000; - statbuf->st_ino = 33333; + setRegularFileStat(statbuf, 3000, 33333); return 0; }); @@ -1818,11 +1818,11 @@ TEST_F(dcaTestFixture, getDCAResultsInVector_TruncatedInPlace_NoRotatedFile) EXPECT_CALL(*g_fileIOMock, fstat(_, _)) .Times(2) .WillOnce([](int fd, struct stat* statbuf) { - statbuf->st_size = 800; // Truncated file size, smaller than stored seek 5000 + setRegularFileStat(statbuf, 800, 12345); return 0; }) .WillOnce([](int fd, struct stat* statbuf) { - statbuf->st_size = 800; + setRegularFileStat(statbuf, 800, 12345); return 0; }); @@ -1920,11 +1920,11 @@ TEST_F(dcaTestFixture, getGrepResults_success) EXPECT_CALL(*g_fileIOMock, fstat(_, _)) .Times(2) .WillOnce([](int fd, struct stat* statbuf) { - statbuf->st_size = 1300; // Set file size + setRegularFileStat(statbuf, 1300, 12345); return 0; // Success }) .WillOnce([](int fd, struct stat* statbuf) { - statbuf->st_size = 1300; // Set file size + setRegularFileStat(statbuf, 1300, 12345); return 0; // Success }); diff --git a/source/test/t2parser/t2parserxconfTest.cpp b/source/test/t2parser/t2parserxconfTest.cpp index 9f6b2864..dffca759 100644 --- a/source/test/t2parser/t2parserxconfTest.cpp +++ b/source/test/t2parser/t2parserxconfTest.cpp @@ -29,7 +29,7 @@ TEST(T2ParserXConf, DefaultScheduleUsed) "{" " \"urn:settings:TelemetryProfile\": {" " \"telemetryProfile:name\": \"DefaultSched\"," - " \"uploadRepository:URL\": \"http://example.com/upload\"," + " \"uploadRepository:URL\": \"https://example.com/upload\"," " \"schedule\": \"11 4 * * *\"," " \"telemetryProfile\": [ {" " \"header\": \"param1\"," @@ -44,7 +44,7 @@ TEST(T2ParserXConf, DefaultScheduleUsed) // default should be 15 minutes -> 900 seconds EXPECT_EQ(900, profile->reportingInterval); ASSERT_NE(profile->t2HTTPDest, nullptr); - EXPECT_STREQ("http://example.com/upload", profile->t2HTTPDest->URL ? profile->t2HTTPDest->URL : ""); + EXPECT_STREQ("https://example.com/upload", profile->t2HTTPDest->URL ? profile->t2HTTPDest->URL : ""); // minimal cleanup (full deep-free is not attempted here) // tests rely on process teardown to reclaim memory in CI runs } @@ -61,7 +61,7 @@ TEST(T2ParserXConf, SlashScheduleAndTopLogHandling) "{" " \"urn:settings:TelemetryProfile\": {" " \"telemetryProfile:name\": \"SlashSched\"," - " \"uploadRepository:URL\": \"http://example.com/upload2\"," + " \"uploadRepository:URL\": \"https://example.com/upload2\"," " \"schedule\": \"0/5 * * * *\"," " \"telemetryProfile\": [" " {" @@ -84,8 +84,141 @@ TEST(T2ParserXConf, SlashScheduleAndTopLogHandling) // schedule "0/5 * * * *" should parse to 5 minutes => 300 sec EXPECT_EQ(300, profile->reportingInterval); ASSERT_NE(profile->t2HTTPDest, nullptr); - EXPECT_STREQ("http://example.com/upload2", profile->t2HTTPDest->URL ? profile->t2HTTPDest->URL : ""); + EXPECT_STREQ("https://example.com/upload2", profile->t2HTTPDest->URL ? profile->t2HTTPDest->URL : ""); // Ensure the profile name got set EXPECT_STREQ("SlashSched", profile->name ? profile->name : ""); } +TEST(T2ParserXConf, RejectsInvalidUploadUrl) +{ + ProfileXConf *profile = NULL; + const char *json_invalid_url = + "{" + " \"urn:settings:TelemetryProfile\": {" + " \"telemetryProfile:name\": \"BadUrl\"," + " \"uploadRepository:URL\": \"http://localhost/test\"," + " \"schedule\": \"0/5 * * * *\"," + " \"telemetryProfile\": [ {" + " \"header\": \"param1\"," + " \"content\": \"alias1\"," + " \"type\": \"/var/log/some.log\"," + " \"pollingFrequency\": \"0\"" + " } ]" + " }" + "}"; + EXPECT_EQ(T2ERROR_FAILURE, processConfigurationXConf((char*)json_invalid_url, &profile)); + EXPECT_EQ((ProfileXConf*)NULL, profile); +} + +TEST(T2ParserXConf, RejectsNumericLoopbackUploadUrls) +{ + ProfileXConf *profile = NULL; + + const char *json_v4_loopback = + "{" + " \"urn:settings:TelemetryProfile\": {" + " \"telemetryProfile:name\": \"LoopbackV4\"," + " \"uploadRepository:URL\": \"https://127.0.0.1/upload\"," + " \"schedule\": \"0/5 * * * *\"," + " \"telemetryProfile\": [ {" + " \"header\": \"param1\"," + " \"content\": \"alias1\"," + " \"type\": \"/var/log/some.log\"," + " \"pollingFrequency\": \"0\"" + " } ]" + " }" + "}"; + EXPECT_EQ(T2ERROR_FAILURE, processConfigurationXConf((char*)json_v4_loopback, &profile)); + EXPECT_EQ((ProfileXConf*)NULL, profile); + + profile = NULL; + const char *json_v4_shortform_loopback = + "{" + " \"urn:settings:TelemetryProfile\": {" + " \"telemetryProfile:name\": \"LoopbackV4Short\"," + " \"uploadRepository:URL\": \"https://127.1/upload\"," + " \"schedule\": \"0/5 * * * *\"," + " \"telemetryProfile\": [ {" + " \"header\": \"param1\"," + " \"content\": \"alias1\"," + " \"type\": \"/var/log/some.log\"," + " \"pollingFrequency\": \"0\"" + " } ]" + " }" + "}"; + EXPECT_EQ(T2ERROR_FAILURE, processConfigurationXConf((char*)json_v4_shortform_loopback, &profile)); + EXPECT_EQ((ProfileXConf*)NULL, profile); + + profile = NULL; + const char *json_v4_decimal_loopback = + "{" + " \"urn:settings:TelemetryProfile\": {" + " \"telemetryProfile:name\": \"LoopbackV4Decimal\"," + " \"uploadRepository:URL\": \"https://2130706433/upload\"," + " \"schedule\": \"0/5 * * * *\"," + " \"telemetryProfile\": [ {" + " \"header\": \"param1\"," + " \"content\": \"alias1\"," + " \"type\": \"/var/log/some.log\"," + " \"pollingFrequency\": \"0\"" + " } ]" + " }" + "}"; + EXPECT_EQ(T2ERROR_FAILURE, processConfigurationXConf((char*)json_v4_decimal_loopback, &profile)); + EXPECT_EQ((ProfileXConf*)NULL, profile); + + profile = NULL; + const char *json_v4_hex_loopback = + "{" + " \"urn:settings:TelemetryProfile\": {" + " \"telemetryProfile:name\": \"LoopbackV4Hex\"," + " \"uploadRepository:URL\": \"https://0x7f000001/upload\"," + " \"schedule\": \"0/5 * * * *\"," + " \"telemetryProfile\": [ {" + " \"header\": \"param1\"," + " \"content\": \"alias1\"," + " \"type\": \"/var/log/some.log\"," + " \"pollingFrequency\": \"0\"" + " } ]" + " }" + "}"; + EXPECT_EQ(T2ERROR_FAILURE, processConfigurationXConf((char*)json_v4_hex_loopback, &profile)); + EXPECT_EQ((ProfileXConf*)NULL, profile); + + profile = NULL; + const char *json_v6_loopback = + "{" + " \"urn:settings:TelemetryProfile\": {" + " \"telemetryProfile:name\": \"LoopbackV6\"," + " \"uploadRepository:URL\": \"https://[::1]/upload\"," + " \"schedule\": \"0/5 * * * *\"," + " \"telemetryProfile\": [ {" + " \"header\": \"param1\"," + " \"content\": \"alias1\"," + " \"type\": \"/var/log/some.log\"," + " \"pollingFrequency\": \"0\"" + " } ]" + " }" + "}"; + EXPECT_EQ(T2ERROR_FAILURE, processConfigurationXConf((char*)json_v6_loopback, &profile)); + EXPECT_EQ((ProfileXConf*)NULL, profile); + + profile = NULL; + const char *json_v4_mapped_loopback = + "{" + " \"urn:settings:TelemetryProfile\": {" + " \"telemetryProfile:name\": \"LoopbackMapped\"," + " \"uploadRepository:URL\": \"https://[::ffff:127.0.0.1]/upload\"," + " \"schedule\": \"0/5 * * * *\"," + " \"telemetryProfile\": [ {" + " \"header\": \"param1\"," + " \"content\": \"alias1\"," + " \"type\": \"/var/log/some.log\"," + " \"pollingFrequency\": \"0\"" + " } ]" + " }" + "}"; + EXPECT_EQ(T2ERROR_FAILURE, processConfigurationXConf((char*)json_v4_mapped_loopback, &profile)); + EXPECT_EQ((ProfileXConf*)NULL, profile); +} + diff --git a/source/utils/persistence.c b/source/utils/persistence.c index 3b3422e4..f141e6ba 100644 --- a/source/utils/persistence.c +++ b/source/utils/persistence.c @@ -24,6 +24,7 @@ #include #include #include +#include #ifdef LIBSYSWRAPPER_BUILD #include "secure_wrapper.h" #endif @@ -45,6 +46,38 @@ static void persistReportMethodInit( ) } } +static bool isValidProfileName(const char *name) +{ + if (name == NULL || *name == '\0') + { + return false; + } + + if (strcmp(name, ".") == 0 || strcmp(name, "..") == 0) + { + return false; + } + + if (strstr(name, "..") != NULL) + { + return false; + } + + if (strchr(name, '/') != NULL || strchr(name, '\\') != NULL) + { + return false; + } + + for (const unsigned char *p = (const unsigned char *)name; *p != '\0'; ++p) + { + if (*p < 0x20 || *p == 0x7f) + { + return false; + } + } + + return true; +} T2ERROR fetchLocalConfigs(const char* path, Vector *configList) { @@ -159,6 +192,13 @@ T2ERROR saveConfigToFile(const char* path, const char *profileName, const char* { return T2ERROR_INVALID_ARGS; } + + if (!isValidProfileName(profileName)) + { + T2Error("Refusing to use unsafe profile name for persistence: %s\n", profileName); + return T2ERROR_FAILURE; + } + FILE *fp = NULL; char filePath[256] = {'\0'}; T2Debug("%s ++in\n", __FUNCTION__); @@ -198,6 +238,13 @@ T2ERROR MsgPackSaveConfig(const char* path, const char *fileName, const char *ms { return T2ERROR_INVALID_ARGS; } + + if (!isValidProfileName(fileName)) + { + T2Error("Refusing to use unsafe persistence file name: %s\n", fileName); + return T2ERROR_FAILURE; + } + FILE *fp; char filePath[256] = {'\0'}; @@ -258,6 +305,13 @@ void removeProfileFromDisk(const char* path, const char* fileName) { return; } + + if (!isValidProfileName(fileName)) + { + T2Error("Refusing to remove unsafe profile path component: %s\n", fileName); + return; + } + size_t len = strlen(path) + strlen(fileName) + 1; char *str = malloc(len); if (! str) @@ -287,6 +341,12 @@ T2ERROR saveCachedReportToPersistenceFolder(const char *profileName, Vector *rep return ret ; } + if (!isValidProfileName(profileName)) + { + T2Error("Refusing to cache report for unsafe profile name: %s\n", profileName); + return T2ERROR_FAILURE; + } + // Lock a mutex to write to common file pthread_once(&persistCahedReporMutexOnce, persistReportMethodInit); @@ -358,6 +418,13 @@ T2ERROR populateCachedReportList(const char *profileName, Vector *outReportList) return ret ; } + if (!isValidProfileName(profileName)) + { + T2Error("Refusing to read cached report for unsafe profile name: %s\n", profileName); + T2Debug("%s --out\n", __FUNCTION__); + return T2ERROR_FAILURE; + } + FILE *filePtr = NULL ; char absFilePath[MAX_FILENAME_LENGTH] = {'0'}; diff --git a/source/utils/t2common.c b/source/utils/t2common.c index 8296a48f..46f5b1ff 100644 --- a/source/utils/t2common.c +++ b/source/utils/t2common.c @@ -19,12 +19,88 @@ #include #include +#include +#include +#include #include "t2common.h" #include "t2log_wrapper.h" #include "dcautil.h" bool whoami_support = false; +int openSecureTempFile(const char *path, bool truncateExisting) +{ + int fd = -1; + + if (path == NULL || *path == '\0') + { + return -1; + } + + fd = open(path, O_WRONLY | O_CREAT | O_NOFOLLOW | O_NONBLOCK, + S_IRUSR | S_IWUSR); + if (fd < 0) + { + T2Error("Unable to safely open %s: %s\n", path, strerror(errno)); + return -1; + } + + struct stat st; + if (fstat(fd, &st) != 0 || !S_ISREG(st.st_mode) || st.st_uid != geteuid()) + { + T2Error("Refusing to use unsafe temp file: %s\n", path); + close(fd); + return -1; + } + + if (fchmod(fd, S_IRUSR | S_IWUSR) != 0) + { + T2Error("Unable to set 0600 permissions on %s: %s\n", path, strerror(errno)); + close(fd); + return -1; + } + + if (truncateExisting && ftruncate(fd, 0) != 0) + { + T2Error("Unable to truncate %s: %s\n", path, strerror(errno)); + close(fd); + return -1; + } + + return fd; +} + +bool createSecureTempFlag(const char *path) +{ + if (path == NULL || *path == '\0') + { + return false; + } + + int fd = open(path, O_WRONLY | O_CREAT | O_EXCL | O_NOFOLLOW, S_IRUSR | S_IWUSR); + if (fd < 0) + { + if (errno == EEXIST) + { + T2Debug("Existing secure flag already present: %s\n", path); + return true; + } + + T2Error("Unable to create secure flag %s: %s\n", path, strerror(errno)); + return false; + } + + if (fchmod(fd, S_IRUSR | S_IWUSR) != 0) + { + T2Error("Unable to set 0600 permissions on %s: %s\n", path, strerror(errno)); + close(fd); + return false; + } + + close(fd); + return true; +} + void freeParam(void *data) { if(data != NULL) diff --git a/source/utils/t2common.h b/source/utils/t2common.h index d4496a40..66b0d5c7 100644 --- a/source/utils/t2common.h +++ b/source/utils/t2common.h @@ -23,6 +23,7 @@ #include #include #include +#include #include "vector.h" #define MAX_ACCUMULATE 20 @@ -185,6 +186,9 @@ int compareLogFileNames(const void *g1, const void *g2); bool getDevicePropertyData(const char *dev_prop_name, char *out_data, unsigned int buff_size); +int openSecureTempFile(const char *path, bool truncateExisting); +bool createSecureTempFlag(const char *path); + void initWhoamiSupport(void); bool isWhoAmiEnabled(void); diff --git a/test/functional-tests/tests/test_multiprofile_msgpacket.py b/test/functional-tests/tests/test_multiprofile_msgpacket.py index 2dd6c24c..757fbdd3 100644 --- a/test/functional-tests/tests/test_multiprofile_msgpacket.py +++ b/test/functional-tests/tests/test_multiprofile_msgpacket.py @@ -273,7 +273,7 @@ def test_for_invalid_activation_timeout(): assert "MODEL_NAME\":\"NULL" in grep_T2logs("cJSON Report ") # 239 - Support for reportEmpty of profiles assert "TR_AC6919" in grep_T2logs("firstreporting interval is given") # 240 - Support for First Reporting Interval -1 assert "5 sec" in grep_T2logs("firstreporting interval is given") # 240 - Support for First Reporting Interval - 2 - assert "NEW TEST PROFILE" in grep_T2logs(LOG_PROFILE_SET) # 101 - Report fetch and parse via HTTP + assert "NEW_TEST_PROFILE" in grep_T2logs(LOG_PROFILE_SET) # 101 - Report fetch and parse via HTTP assert "60 sec" in grep_T2logs("reporting interval is taken - NEW TEST PROFILE") # 107 - Configurable reporting interval # 241 - Support for DCM profile and multiprofile parallel execution assert "AccountId\":\"Platform_Container_Test_DEVICE" in grep_T2logs("cJSON Report ") # 102 - Include data from data source as TR181 Parameter diff --git a/test/functional-tests/tests/test_xconf_communications.py b/test/functional-tests/tests/test_xconf_communications.py index 99a10522..3b706edd 100644 --- a/test/functional-tests/tests/test_xconf_communications.py +++ b/test/functional-tests/tests/test_xconf_communications.py @@ -96,7 +96,7 @@ def test_change_profile(): kill_telemetry(12) sleep(10) assert os.path.exists(XCONF_PERSISTANT_FILE) - assert "NEW TEST PROFILE" in grep_T2logs("NEW TEST PROFILE") + assert "NEW_TEST_PROFILE" in grep_T2logs("NEW_TEST_PROFILE") @pytest.mark.run(order=6) def test_verify_urlencoding(): @@ -118,7 +118,7 @@ def test_verify_schedule(): command = ["echo 'This is Test Log' > /opt/logs/test.log"] run_shell_command(command) sleep(60) - ERROR_MSG = "Waiting for 60 sec for next TIMEOUT for profile as reporting interval is taken - NEW TEST PROFILE" + ERROR_MSG = "Waiting for 60 sec for next TIMEOUT for profile as reporting interval is taken - NEW_TEST_PROFILE" assert ERROR_MSG in grep_T2logs(ERROR_MSG) @pytest.mark.run(order=8) @@ -131,14 +131,14 @@ def test_verify_markers(): @pytest.mark.run(order=9) def test_verify_report(): - ERROR_MSG1 = "CollectAndReportXconf ++in profileName : NEW TEST PROFILE" + ERROR_MSG1 = "CollectAndReportXconf ++in profileName : NEW_TEST_PROFILE" ERROR_MSG2 = "cJSON Report =" assert ERROR_MSG1 in grep_T2logs(ERROR_MSG1) assert ERROR_MSG2 in grep_T2logs(ERROR_MSG2) @pytest.mark.run(order=10) def test_log_upload(): - LOG_MSG1 = "CollectAndReportXconf ++in profileName : NEW TEST PROFILE" + LOG_MSG1 = "CollectAndReportXconf ++in profileName : NEW_TEST_PROFILE" LOG_MSG2 = "cJSON Report =" clear_T2logs() kill_telemetry(10) @@ -148,7 +148,7 @@ def test_log_upload(): @pytest.mark.run(order=11) def test_log_upload_on_demand(): - LOG_MSG1 = "CollectAndReportXconf ++in profileName : NEW TEST PROFILE" + LOG_MSG1 = "CollectAndReportXconf ++in profileName : NEW_TEST_PROFILE" LOG_MSG2 = "cJSON Report =" clear_T2logs() kill_telemetry(29)