Description: The directory is created based on waht is received from outside, without proper checking, the user can escape the directory, with a directory traversal attack Author: Gianfranco Costamagna Forwarded: yes, email Last-Update: 2026-10-29 --- liblivemedia-2026.10.24.orig/mediaServer/DynamicRTSPServer.cpp +++ liblivemedia-2026.10.24/mediaServer/DynamicRTSPServer.cpp @@ -18,10 +18,97 @@ along with this library; if not, write t // based on whether or not the specified stream name exists as a file // Implementation +#ifdef _WIN32 +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include +#include +#include +#endif #include "DynamicRTSPServer.hh" #include +#include #include +#ifdef _WIN32 +static char* finalPathForHandle(HANDLE handle) { + DWORD const pathSize = GetFinalPathNameByHandleA(handle, NULL, 0, FILE_NAME_NORMALIZED); + if (pathSize == 0) return NULL; + + char* path = new char[pathSize + 1]; + DWORD const charsWritten = GetFinalPathNameByHandleA(handle, path, pathSize + 1, + FILE_NAME_NORMALIZED); + if (charsWritten == 0 || charsWritten > pathSize) { + delete[] path; + return NULL; + } + return path; +} + +static Boolean getConfinedFilePath(char const* streamName, FILE* fid, char*& filePath) { + (void)streamName; + HANDLE const rootHandle = CreateFileA(".", FILE_READ_ATTRIBUTES, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); + if (rootHandle == INVALID_HANDLE_VALUE) return False; + + char* rootPath = finalPathForHandle(rootHandle); + CloseHandle(rootHandle); + if (rootPath == NULL) return False; + + intptr_t const fileHandle = _get_osfhandle(_fileno(fid)); + if (fileHandle == -1) { + delete[] rootPath; + return False; + } + + filePath = finalPathForHandle((HANDLE)fileHandle); + if (filePath == NULL) { + delete[] rootPath; + return False; + } + + size_t const rootLength = strlen(rootPath); + Boolean const isWithinRoot = _strnicmp(filePath, rootPath, rootLength) == 0 + && (rootPath[rootLength - 1] == '\\' || rootPath[rootLength - 1] == '/' + || filePath[rootLength] == '\0' || filePath[rootLength] == '\\' + || filePath[rootLength] == '/'); + delete[] rootPath; + if (!isWithinRoot) { + delete[] filePath; + filePath = NULL; + return False; + } + + return True; +} +#else +static Boolean getConfinedFilePath(char const* streamName, FILE* /*fid*/, char*& filePath) { + char* rootPath = realpath(".", NULL); + if (rootPath == NULL) return False; + + filePath = realpath(streamName, NULL); + if (filePath == NULL) { + free(rootPath); + return False; + } + + size_t const rootLength = strlen(rootPath); + Boolean const isWithinRoot = strncmp(filePath, rootPath, rootLength) == 0 + && (rootPath[rootLength - 1] == '/' + || filePath[rootLength] == '\0' || filePath[rootLength] == '/'); + free(rootPath); + if (!isWithinRoot) { + free(filePath); + filePath = NULL; + return False; + } + + return True; +} +#endif + DynamicRTSPServer* DynamicRTSPServer::createNew(UsageEnvironment& env, Port ourPort, UserAuthenticationDatabase* authDatabase, @@ -44,7 +131,8 @@ DynamicRTSPServer::~DynamicRTSPServer() } static ServerMediaSession* createNewSMS(UsageEnvironment& env, - char const* fileName, FILE* fid); // forward + char const* streamName, + char const* filePath, FILE* fid); // forward void DynamicRTSPServer ::lookupServerMediaSession(char const* streamName, @@ -53,7 +141,13 @@ void DynamicRTSPServer Boolean isFirstLookupInSession) { // First, check whether the specified "streamName" exists as a local file: FILE* fid = fopen(streamName, "rb"); - Boolean const fileExists = fid != NULL; + char* filePath = NULL; + Boolean const fileExists = fid != NULL + && getConfinedFilePath(streamName, fid, filePath); + if (fid != NULL && !fileExists) { + fclose(fid); + fid = NULL; + } // Next, check whether we already have a "ServerMediaSession" for this file: ServerMediaSession* sms = getServerMediaSession(streamName); @@ -76,13 +170,19 @@ void DynamicRTSPServer } if (sms == NULL) { - sms = createNewSMS(envir(), streamName, fid); + sms = createNewSMS(envir(), streamName, filePath, fid); addServerMediaSession(sms); } fclose(fid); } +#ifndef _WIN32 + free(filePath); +#else + delete[] filePath; +#endif + if (completionFunc != NULL) { (*completionFunc)(completionClientData, sms); } @@ -115,10 +215,11 @@ static void onOggDemuxCreation(OggFileSe #define NEW_SMS(description) do {\ char const* descStr = description\ ", streamed by the LIVE555 Media Server";\ -sms = ServerMediaSession::createNew(env, fileName, fileName, descStr);\ +sms = ServerMediaSession::createNew(env, streamName, streamName, descStr);\ } while(0) static ServerMediaSession* createNewSMS(UsageEnvironment& env, + char const* streamName, char const* fileName, FILE* /*fid*/) { // Use the file name extension to determine the type of "ServerMediaSession": char const* extension = strrchr(fileName, '.');