CVE-2022-3976: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal') (CWE-22)
value: A vulnerability has been found in MZ Automation libiec61850 up to 1.4 and classified as critical. This vulnerability affects unknown code of the file src/mms/iso_mms/client/mms_client_files.c of the component MMS File Services. The manipulation of the argument filename leads to path traversal. Upgrading to version 1.5 is able to address this issue. The name of the patch is 10622ba36bb3910c151348f1569f039ecdd8786f. It is recommended to upgrade the affected component. The identifier of this vulnerability is VDB-213556.
CWE description: The product uses external input to construct a pathname that is intended to identify a file or directory that is located underneath a restricted parent directory, but the product does not properly neutralize special elements within the pathname that can cause the pathname to resolve to a location that is outside of the restricted directory.
CVSS v3 base score: 8.8
Published: 2022-11-13T14:15Z
Repository: https://github.com/mz-automation/libiec61850
Language: C
Files changed: hal/filesystem/linux/file_provider_linux.c, src/mms/inc_private/mms_common_internal.h, src/mms/iso_mms/client/mms_client_files.c, src/mms/iso_mms/common/mms_common_msg.c, src/mms/iso_mms/server/mms_file_service.c
Commit date: 2022-10-07 17:09:11 +0100
Commit message: - fixed path traversal vulnerability in MMS file services (LIB61850-357)

Diff: diff --git a/hal/filesystem/linux/file_provider_linux.c b/hal/filesystem/linux/file_provider_linux.c
index dc869d96..7b501f00 100644
--- a/hal/filesystem/linux/file_provider_linux.c
+++ b/hal/filesystem/linux/file_provider_linux.c
@@ -35,11 +35,10 @@
 
 struct sDirectoryHandle {
     DIR* handle;
 };
 
-
 FileHandle
 FileSystem_openFile(char* fileName, bool readWrite)
 {
     FileHandle newHandle = NULL;
 
diff --git a/src/mms/inc_private/mms_common_internal.h b/src/mms/inc_private/mms_common_internal.h
index 6ce05aeb..f096e7bc 100644
--- a/src/mms/inc_private/mms_common_internal.h
+++ b/src/mms/inc_private/mms_common_internal.h
@@ -80,10 +80,13 @@ LIB61850_INTERNAL void
 mmsMsg_createExtendedFilename(const char* basepath, int bufSize, char* extendedFileName, char* fileName);
 
 LIB61850_INTERNAL FileHandle
 mmsMsg_openFile(const char* basepath, char* fileName, bool readWrite);
 
+LIB61850_INTERNAL bool
+mmsMsg_isFilenameSave(const char* filename);
+
 #endif /* (MMS_FILE_SERVICE == 1) */
 
 typedef struct sMmsServiceError
 {
     int errorClass;
diff --git a/src/mms/iso_mms/client/mms_client_files.c b/src/mms/iso_mms/client/mms_client_files.c
index eda9bb1f..4fca418e 100644
--- a/src/mms/iso_mms/client/mms_client_files.c
+++ b/src/mms/iso_mms/client/mms_client_files.c
@@ -69,11 +69,10 @@ getFrsm(MmsConnection connection, int32_t frsmId)
     }
 
     return frsm;
 }
 
-
 static int32_t
 getNextFrsmId(MmsConnection connection)
 {
     uint32_t nextFrsmId = connection->nextFrsmId;
     connection->nextFrsmId++;
@@ -123,42 +122,52 @@ mmsClient_handleFileOpenRequest(
         }
     }
 
     if (hasFileName) {
 
-        MmsFileReadStateMachine* frsm = getFreeFrsm(connection);
+        if (mmsMsg_isFilenameSave(filename) == false) {
+            /* potential attack */
 
-        if (frsm != NULL) {
+            if (DEBUG_MMS_CLIENT)
+                printf("MMS_CLIENT: client provided unsave filename -> rejected\n");
 
-            MmsOutstandingCall obtainFileCall = mmsClient_getMatchingObtainFileRequest(connection, filename);
+             mmsMsg_createServiceErrorPdu(invokeId, response, MMS_ERROR_FILE_FILE_NON_EXISTENT);
+        }
+        else {
+            MmsFileReadStateMachine* frsm = getFreeFrsm(connection);
 
-            if (obtainFileCall) {
+            if (frsm != NULL) {
 
-                if (DEBUG_MMS_CLIENT)
-                    printf("MMS_CLIENT: file open is matching obtain file request for file %s\n", filename);
+                MmsOutstandingCall obtainFileCall = mmsClient_getMatchingObtainFileRequest(connection, filename);
 
-                obtainFileCall->timeout = Hal_getTimeInMs() + connection->requestTimeout;
-            }
+                if (obtainFileCall) {
 
-            FileHandle fileHandle = mmsMsg_openFile(MmsConnection_getFilestoreBasepath(connection), filename, false);
+                    if (DEBUG_MMS_CLIENT)
+                        printf("MMS_CLIENT: file open is matching obtain file request for file %s\n", filename);
 
-            if (fileHandle != NULL) {
+                    obtainFileCall->timeout = Hal_getTimeInMs() + connection->requestTimeout;
+                }
 
-                frsm->fileHandle = fileHandle;
-                frsm->readPosition = filePosition;
-                frsm->frsmId = getNextFrsmId(connection);
-                frsm->obtainRequest = obtainFileCall;
+                FileHandle fileHandle = mmsMsg_openFile(MmsConnection_getFilestoreBasepath(connection), filename, false);
+
+                if (fileHandle != NULL) {
+
+                    frsm->fileHandle = fileHandle;
+                    frsm->readPosition = filePosition;
+                    frsm->frsmId = getNextFrsmId(connection);
+                    frsm->obtainRequest = obtainFileCall;
+
+                    mmsMsg_createFileOpenResponse(MmsConnection_getFilestoreBasepath(connection),
+                            invokeId, response, filename, frsm);
+                }
+                else
+                    mmsMsg_createServiceErrorPdu(invokeId, response, MMS_ERROR_FILE_FILE_NON_EXISTENT);
 
-                mmsMsg_createFileOpenResponse(MmsConnection_getFilestoreBasepath(connection),
-                        invokeId, response, filename, frsm);
             }
             else
-                mmsMsg_createServiceErrorPdu(invokeId, response, MMS_ERROR_FILE_FILE_NON_EXISTENT);
-
+                mmsMsg_createServiceErrorPdu(invokeId, response, MMS_ERROR_RESOURCE_OTHER);
         }
-        else
-            mmsMsg_createServiceErrorPdu(invokeId, response, MMS_ERROR_RESOURCE_OTHER);
     }
     else
         goto exit_invalid_parameter;
 
     return;
diff --git a/src/mms/iso_mms/common/mms_common_msg.c b/src/mms/iso_mms/common/mms_common_msg.c
index 7555f0c0..76657334 100644
--- a/src/mms/iso_mms/common/mms_common_msg.c
+++ b/src/mms/iso_mms/common/mms_common_msg.c
@@ -568,10 +568,28 @@ mmsMsg_createExtendedFilename(const char* basepath, int bufSize, char* extendedF
 #else
     StringUtils_concatString(extendedFileName, bufSize, CONFIG_VIRTUAL_FILESTORE_BASEPATH, fileName);
 #endif
 }
 
+bool
+mmsMsg_isFilenameSave(const char* filename)
+{
+    if (filename)
+    {
+        if (strstr(filename, ".."))
+            return false;
+
+        if (strstr(filename, "./"))
+            return false;
+
+        return true;
+    }
+    else {
+        return false;
+    }
+}
+
 FileHandle
 mmsMsg_openFile(const char* basepath, char* fileName, bool readWrite)
 {
 #if (CONFIG_SET_FILESTORE_BASEPATH_AT_RUNTIME == 1)
     char extendedFileName[512];
@@ -618,11 +636,11 @@ mmsMsg_parseFileName(char* filename, uint8_t* buffer, int* bufPos, int maxBufPos
     /* Check if path contains invalid characters (prevent escaping the virtual filestore by using "..")
      * TODO this may be platform dependent. Also depending of the platform there might be other evil
      * characters.
      */
     if (strstr(filename, "..") != NULL) {
-        mmsMsg_createServiceErrorPdu(invokeId, response, MMS_ERROR_FILE_FILENAME_SYNTAX_ERROR);
+        mmsMsg_createServiceErrorPdu(invokeId, response, MMS_ERROR_FILE_FILE_NON_EXISTENT);
         return false;
     }
 
     return true;
 }
diff --git a/src/mms/iso_mms/server/mms_file_service.c b/src/mms/iso_mms/server/mms_file_service.c
index 2a0b18ad..e4ae154b 100644
--- a/src/mms/iso_mms/server/mms_file_service.c
+++ b/src/mms/iso_mms/server/mms_file_service.c
@@ -233,12 +233,10 @@ mmsMsg_createFileOpenResponse(const char* basepath, uint32_t invokeId, ByteBuffe
     bufPos = encodeFileAttributes(0xa1, frsm->fileSize, gtString, buffer, bufPos);
 
     response->size = bufPos;
 }
 
-
-
 void
 mmsServer_handleFileDeleteRequest(
     MmsServerConnection connection,
     uint8_t* buffer, int bufPos, int maxBufPos,
     uint32_t invokeId,
@@ -264,10 +262,20 @@ mmsServer_handleFileDeleteRequest(
     filename[length] = 0;
 
     if (DEBUG_MMS_SERVER)
         printf("MMS_SERVER: mms_file_service.c: Delete file (%s)\n", filename);
 
+    if (mmsMsg_isFilenameSave(filename) == false)
+    {
+        if (DEBUG_MMS_SERVER)
+            printf("MMS_SERVER: remote provided unsave filename -> rejected\n");
+
+        mmsMsg_createServiceErrorPdu(invokeId, response, MMS_ERROR_FILE_FILE_NON_EXISTENT);
+
+        return;
+    }
+
     if (connection->server->fileAccessHandler != NULL) {
         MmsError access = connection->server->fileAccessHandler(connection->server->fileAccessHandlerParameter,
                             connection, MMS_FILE_ACCESS_TYPE_DELETE, filename, NULL);
 
         if (access != MMS_ERROR_NONE) {
@@ -342,10 +350,21 @@ mmsServer_handleFileOpenRequest(
         }
     }
 
     if (hasFileName) {
 
+        if (mmsMsg_isFilenameSave(filename) == false) {
+            /* potential attack */
+
+            if (DEBUG_MMS_CLIENT)
+                printf("MMS_SERVER: remote provided unsave filename -> rejected\n");
+
+             mmsMsg_createServiceErrorPdu(invokeId, response, MMS_ERROR_FILE_FILE_NON_EXISTENT);
+
+             return;
+        }
+
         if (connection->server->fileAccessHandler != NULL) {
             MmsError access = connection->server->fileAccessHandler(connection->server->fileAccessHandlerParameter,
                                 connection, MMS_FILE_ACCESS_TYPE_OPEN, filename, NULL);
 
             if (access != MMS_ERROR_NONE) {
@@ -688,10 +707,19 @@ mmsServer_handleObtainFileRequest(
         }
     }
 
     if (hasSourceFileName && hasDestinationFilename) {
 
+        if (mmsMsg_isFilenameSave(destinationFilename) == false) {
+            /* potential attack */
+
+            if (DEBUG_MMS_SERVER)
+                printf("MMS_SERVER: remote provided unsave filename -> rejected\n");
+
+            goto exit_invalid_parameter;
+        }
+
         /* Call user to check if access is allowed */
         if (connection->server->fileAccessHandler != NULL) {
             MmsError access = connection->server->fileAccessHandler(connection->server->fileAccessHandlerParameter,
                                 connection, MMS_FILE_ACCESS_TYPE_OBTAIN, destinationFilename, sourceFilename);
 
@@ -1021,10 +1049,21 @@ createFileDirectoryResponse(const char* basepath, uint32_t invokeId, ByteBuffer*
     if (continueAfterFileName != NULL) {
         if (strlen(continueAfterFileName) == 0)
             continueAfterFileName = NULL;
     }
 
+    if ((directoryName && mmsMsg_isFilenameSave(directoryName) == false) || 
+        (continueAfterFileName && mmsMsg_isFilenameSave(continueAfterFileName) == false))
+    {
+        if (DEBUG_MMS_SERVER)
+            printf("MMS_SERVER: remote provided unsave filename -> rejected\n");
+
+       mmsMsg_createServiceErrorPdu(invokeId, response, MMS_ERROR_FILE_FILE_NON_EXISTENT);
+
+       return;
+    }
+
     tempCurPos = addFileEntriesToResponse(basepath, buffer, tempCurPos, maxSize, directoryName, &continueAfterFileName, &moreFollows);
 
 	if (tempCurPos < 0) {
 
        if (DEBUG_MMS_SERVER)
@@ -1126,10 +1165,20 @@ mmsServer_handleFileRenameRequest(
         }
     }
 
     if ((strlen(currentFileName) != 0) && (strlen(newFileName) != 0)) {
 
+        if ((mmsMsg_isFilenameSave(currentFileName) == false) || (mmsMsg_isFilenameSave(newFileName) == false))
+        {
+            if (DEBUG_MMS_SERVER)
+                printf("MMS_SERVER: remote provided unsave filename -> rejected\n");
+
+            mmsMsg_createServiceErrorPdu(invokeId, response, MMS_ERROR_FILE_FILE_NON_EXISTENT);
+
+            return;
+        }
+
         /* Call user to check if access is allowed */
         if (connection->server->fileAccessHandler != NULL) {
             MmsError access = connection->server->fileAccessHandler(connection->server->fileAccessHandlerParameter,
                                 connection, MMS_FILE_ACCESS_TYPE_RENAME, currentFileName, newFileName);
 

Vulnerable code: 

        MmsFileReadStateMachine* frsm = getFreeFrsm(connection);
        if (frsm != NULL) {
            MmsOutstandingCall obtainFileCall = mmsClient_getMatchingObtainFileRequest(connection, filename);
            if (obtainFileCall) {
                if (DEBUG_MMS_CLIENT)
                    printf("MMS_CLIENT: file open is matching obtain file request for file %s\n", filename);
                obtainFileCall->timeout = Hal_getTimeInMs() + connection->requestTimeout;
            }
            FileHandle fileHandle = mmsMsg_openFile(MmsConnection_getFilestoreBasepath(connection), filename, false);
            if (fileHandle != NULL) {
                frsm->fileHandle = fileHandle;
                frsm->readPosition = filePosition;
                frsm->frsmId = getNextFrsmId(connection);
                frsm->obtainRequest = obtainFileCall;
                mmsMsg_createFileOpenResponse(MmsConnection_getFilestoreBasepath(connection),
                        invokeId, response, filename, frsm);
                mmsMsg_createServiceErrorPdu(invokeId, response, MMS_ERROR_FILE_FILE_NON_EXISTENT);

        else
            mmsMsg_createServiceErrorPdu(invokeId, response, MMS_ERROR_RESOURCE_OTHER);
        mmsMsg_createServiceErrorPdu(invokeId, response, MMS_ERROR_FILE_FILENAME_SYNTAX_ERROR);


Fixed code: LIB61850_INTERNAL bool
mmsMsg_isFilenameSave(const char* filename);

        if (mmsMsg_isFilenameSave(filename) == false) {
            /* potential attack */
            if (DEBUG_MMS_CLIENT)
                printf("MMS_CLIENT: client provided unsave filename -> rejected\n");
             mmsMsg_createServiceErrorPdu(invokeId, response, MMS_ERROR_FILE_FILE_NON_EXISTENT);
        }
        else {
            MmsFileReadStateMachine* frsm = getFreeFrsm(connection);
            if (frsm != NULL) {
                MmsOutstandingCall obtainFileCall = mmsClient_getMatchingObtainFileRequest(connection, filename);
                if (obtainFileCall) {
                    if (DEBUG_MMS_CLIENT)
                        printf("MMS_CLIENT: file open is matching obtain file request for file %s\n", filename);
                    obtainFileCall->timeout = Hal_getTimeInMs() + connection->requestTimeout;
                }
                FileHandle fileHandle = mmsMsg_openFile(MmsConnection_getFilestoreBasepath(connection), filename, false);

                if (fileHandle != NULL) {

                    frsm->fileHandle = fileHandle;
                    frsm->readPosition = filePosition;
                    frsm->frsmId = getNextFrsmId(connection);
                    frsm->obtainRequest = obtainFileCall;

                    mmsMsg_createFileOpenResponse(MmsConnection_getFilestoreBasepath(connection),
                            invokeId, response, filename, frsm);
                }
                else
                    mmsMsg_createServiceErrorPdu(invokeId, response, MMS_ERROR_FILE_FILE_NON_EXISTENT);
                mmsMsg_createServiceErrorPdu(invokeId, response, MMS_ERROR_RESOURCE_OTHER);
bool
mmsMsg_isFilenameSave(const char* filename)
{
    if (filename)
    {
        if (strstr(filename, ".."))
            return false;

        if (strstr(filename, "./"))
            return false;

        return true;
    }
    else {
        return false;
    }
}

        mmsMsg_createServiceErrorPdu(invokeId, response, MMS_ERROR_FILE_FILE_NON_EXISTENT);
    if (mmsMsg_isFilenameSave(filename) == false)
    {
        if (DEBUG_MMS_SERVER)
            printf("MMS_SERVER: remote provided unsave filename -> rejected\n");

        mmsMsg_createServiceErrorPdu(invokeId, response, MMS_ERROR_FILE_FILE_NON_EXISTENT);

        return;
    }

        if (mmsMsg_isFilenameSave(filename) == false) {
            /* potential attack */

            if (DEBUG_MMS_CLIENT)
                printf("MMS_SERVER: remote provided unsave filename -> rejected\n");

             mmsMsg_createServiceErrorPdu(invokeId, response, MMS_ERROR_FILE_FILE_NON_EXISTENT);

             return;
        }

        if (mmsMsg_isFilenameSave(destinationFilename) == false) {
            /* potential attack */

            if (DEBUG_MMS_SERVER)
                printf("MMS_SERVER: remote provided unsave filename -> rejected\n");

            goto exit_invalid_parameter;
        }

    if ((directoryName && mmsMsg_isFilenameSave(directoryName) == false) || 
        (continueAfterFileName && mmsMsg_isFilenameSave(continueAfterFileName) == false))
    {
        if (DEBUG_MMS_SERVER)
            printf("MMS_SERVER: remote provided unsave filename -> rejected\n");

       mmsMsg_createServiceErrorPdu(invokeId, response, MMS_ERROR_FILE_FILE_NON_EXISTENT);

       return;
    }

        if ((mmsMsg_isFilenameSave(currentFileName) == false) || (mmsMsg_isFilenameSave(newFileName) == false))
        {
            if (DEBUG_MMS_SERVER)
                printf("MMS_SERVER: remote provided unsave filename -> rejected\n");

            mmsMsg_createServiceErrorPdu(invokeId, response, MMS_ERROR_FILE_FILE_NON_EXISTENT);

            return;
        }