Only prepend if not root folder and validate directory source (#154)

This commit is contained in:
Tina Murimi 2023-08-22 17:22:57 +03:00 коммит произвёл GitHub
Родитель a47d40e76f
Коммит 33b1a277e1
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
18 изменённых файлов: 287 добавлений и 115 удалений

1
.gitignore поставляемый
Просмотреть файл

@ -26,6 +26,7 @@ bld/
# Visual Studio 2015/2017 cache/options directory
.vs/
.vscode/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/

Просмотреть файл

@ -1337,6 +1337,65 @@ namespace LogMonitorTests
}
}
TEST_METHOD(TestRootDirectoryConfigurations)
{
const std::wstring directory = L"C:\\";
bool includeSubdirectories = false;
std::wstring configFileStr;
std::wstring configFileStrFormat =
L"{ \
\"LogConfig\": { \
\"sources\": [ \
{\
\"type\": \"File\",\
\"directory\": \"%s\",\
\"includeSubdirectories\": %s\
}\
]\
}\
}";
// Valid: Root dir and includeSubdirectories = false
{
configFileStr = Utility::FormatString(
configFileStrFormat.c_str(),
Utility::ReplaceAll(directory, L"\\", L"\\\\").c_str(),
includeSubdirectories ? L"true" : L"false");
JsonFileParser jsonParser(configFileStr);
LoggerSettings settings;
bool success = ReadConfigFile(jsonParser, settings);
Assert::IsTrue(success);
std::wstring output = RecoverOuput();
Assert::AreEqual(L"", output.c_str());
}
// Invalid: Root dir and includeSubdirectories = true
{
includeSubdirectories = true;
configFileStr = Utility::FormatString(
configFileStrFormat.c_str(),
Utility::ReplaceAll(directory, L"\\", L"\\\\").c_str(),
includeSubdirectories ? L"true" : L"false");
fflush(stdout);
ZeroMemory(bigOutBuf, sizeof(bigOutBuf));
JsonFileParser jsonParser(configFileStr);
LoggerSettings settings;
bool success = ReadConfigFile(jsonParser, settings);
Assert::IsTrue(success);
std::wstring output = RecoverOuput();
Assert::IsTrue(output.find(L"WARNING") != std::wstring::npos);
}
}
///
/// Check that invalid ETW sources are not returned by ReadConfigFile.
///
@ -1557,28 +1616,12 @@ namespace LogMonitorTests
TEST_METHOD(TestInvalidWaitInSeconds) {
std::wstring directory = L"C:\\LogMonitor\\logs";
std::wstring configFileStrFormat =
L"{ \
\"LogConfig\": { \
\"sources\": [ \
{\
\"type\": \"File\",\
\"directory\": \"%s\",\
\"waitInSeconds\": %f\
}\
]\
}\
}";
TestInvalidWaitInSecondsValues(L"-10", false);
TestInvalidWaitInSecondsValues(L"-Inf", true);
}
private:
void TestWaitInSecondsValues(std::wstring waitInSeconds, bool asString = false) {
std::wstring configFileStrFormat;
std::wstring directory = L"C:\\LogMonitor\\logs";
std::wstring configFileStr = GetConfigFileStrFormat(directory, waitInSeconds, asString);

Просмотреть файл

@ -9,7 +9,7 @@
#include "../src/LogMonitor/EtwMonitor.cpp"
#include "../src/LogMonitor/EventMonitor.cpp"
#include "../src/LogMonitor/JsonFileParser.cpp"
#include "../src/LogMonitor/FileMonitor/Utilities.cpp"
#include "../src/LogMonitor/FileMonitor/FileMonitorUtilities.cpp"
#include "../src/LogMonitor/LogFileMonitor.cpp"
#include "../src/LogMonitor/ProcessMonitor.cpp"
#include "../src/LogMonitor/Utility.cpp"

Просмотреть файл

@ -59,7 +59,7 @@
#include "../src/LogMonitor/LogWriter.h"
#include "../src/LogMonitor/EtwMonitor.h"
#include "../src/LogMonitor/EventMonitor.h"
#include "../src/LogMonitor/FileMonitor/Utilities.h"
#include "../src/LogMonitor/FileMonitor/FileMonitorUtilities.h"
#include "../src/LogMonitor/LogFileMonitor.h"
#include "../src/LogMonitor/ProcessMonitor.h"
#include "Utility.h"

Просмотреть файл

@ -273,6 +273,28 @@ This will monitor any changes in log files matching a specified filter, given th
}
```
**Note:** When the directory is the root directory (e.g. C:\\ ) we can only monitor a file that is in the root directory, not a subfolder. This is due to access issues (even when running LogMonitor as an Admin) for some of the folders in the root directory. Therefore, `includeSubdirectories` must be `false` for the root directory. See example below:
```json
{
"LogConfig": {
"sources": [
{
"type": "File",
"directory": "C:",
"filter": "*.log",
"includeSubdirectories": false
}
]
}
}
```
When the root directory is passed and `includeSubdirectories = true`, we get an error:
```
ERROR: LoggerSettings: Invalid Source File atrribute 'directory' (C:) and 'includeSubdirectories' (true).'includeSubdirectories' attribute cannot be 'true' for the root directory
WARNING: Failed to parse configuration file. Error retrieving source attributes. Invalid source
```
## Process Monitoring
### Description

Просмотреть файл

@ -6,6 +6,7 @@
#include "pch.h"
#include "./Parser/ConfigFileParser.h"
#include "./LogWriter.h"
#include "./FileMonitor/FileMonitorUtilities.h"
/// ConfigFileParser.cpp
///
@ -306,8 +307,13 @@ ReadSourceAttributes(
// * directory
// * filter
//
else if (_wcsnicmp(key.c_str(), JSON_TAG_DIRECTORY, _countof(JSON_TAG_DIRECTORY)) == 0
|| _wcsnicmp(key.c_str(), JSON_TAG_FILTER, _countof(JSON_TAG_FILTER)) == 0)
else if (_wcsnicmp(key.c_str(), JSON_TAG_DIRECTORY, _countof(JSON_TAG_DIRECTORY)) == 0)
{
std::wstring directory = Parser.ParseStringValue();
FileMonitorUtilities::ParseDirectoryValue(directory);
Attributes[key] = new std::wstring(directory);
}
else if (_wcsnicmp(key.c_str(), JSON_TAG_FILTER, _countof(JSON_TAG_FILTER)) == 0)
{
Attributes[key] = new std::wstring(Parser.ParseStringValue());
}
@ -396,6 +402,12 @@ ReadSourceAttributes(
} while (Parser.ParseNextObjectElement());
}
bool isSourceFileValid = ValidateDirectoryAttributes(Attributes);
if (!isSourceFileValid)
{
success = false;
}
return success;
}
@ -640,6 +652,36 @@ AddNewSource(
return true;
}
///
/// Validates that when root directory is passed, includeSubdirectories is false
///
/// \param Attributes An AttributesMap that contains the attributes of the new source objet.
/// \return false when root directory is passed, includeSubdirectories = true. Otherwise, true </returns>
bool ValidateDirectoryAttributes(_In_ AttributesMap &Attributes)
{
if (!Utility::ConfigAttributeExists(Attributes, JSON_TAG_DIRECTORY) ||
!Utility::ConfigAttributeExists(Attributes, JSON_TAG_INCLUDE_SUBDIRECTORIES))
{
return true;
}
std::wstring directory = *(std::wstring *)Attributes[JSON_TAG_DIRECTORY];
const bool includeSubdirectories = *(bool *)Attributes[JSON_TAG_INCLUDE_SUBDIRECTORIES];
// Check if Log file monitor config is valid
const bool isValid = FileMonitorUtilities::IsValidSourceFile(directory, includeSubdirectories);
if (!isValid)
{
logWriter.TraceError(
Utility::FormatString(
L"LoggerSettings: Invalid Source File atrribute 'directory' (%s) and 'includeSubdirectories' (%s)."
L"'includeSubdirectories' attribute cannot be 'true' for the root directory",
directory.c_str(), includeSubdirectories ? L"true" : L"false")
.c_str());
}
return isValid;
}
///
/// Debug function
///

Просмотреть файл

@ -4,21 +4,22 @@
//
#include "pch.h"
#include <regex>
/**
* Warapper around Create Event API
*
*
* @param bManualReset
* @param bInitialState
*
*
* return event handle
*/
HANDLE CreateFileMonitorEvent(
_In_ BOOL bManualReset,
_In_ BOOL bInitialState
) {
*/
HANDLE FileMonitorUtilities::CreateFileMonitorEvent(
_In_ BOOL bManualReset,
_In_ BOOL bInitialState)
{
HANDLE event = CreateEvent(nullptr, bManualReset, bInitialState, nullptr);
if(!event)
if (!event)
{
throw std::system_error(std::error_code(GetLastError(), std::system_category()), "CreateEvent");
}
@ -26,25 +27,28 @@ HANDLE CreateFileMonitorEvent(
return event;
}
/**
* @brief Get Log Directory Handle Object
*
*
* @param logDirectory - path to get handle
* @param stopEvent - pass an event to use when we want to stop waiting
*
* @return HANDLE
*
* @return HANDLE
*/
HANDLE GetLogDirHandle(_In_ std::wstring logDirectory, _In_ HANDLE stopEvent, _In_ std::double_t waitInSeconds) {
HANDLE FileMonitorUtilities::GetLogDirHandle(
_In_ std::wstring logDirectory,
_In_ HANDLE stopEvent,
_In_ std::double_t waitInSeconds)
{
DWORD status = ERROR_SUCCESS;
HANDLE logDirHandle = CreateFileW (logDirectory.c_str(),
FILE_LIST_DIRECTORY,
FILE_SHARE_READ | FILE_SHARE_WRITE,
nullptr,
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
nullptr);
HANDLE logDirHandle = CreateFileW(logDirectory.c_str(),
FILE_LIST_DIRECTORY,
FILE_SHARE_READ | FILE_SHARE_WRITE,
nullptr,
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
nullptr);
if (logDirHandle == INVALID_HANDLE_VALUE)
{
@ -54,7 +58,7 @@ HANDLE GetLogDirHandle(_In_ std::wstring logDirectory, _In_ HANDLE stopEvent, _I
if (status == ERROR_FILE_NOT_FOUND ||
status == ERROR_PATH_NOT_FOUND)
{
std::wstring waitLogMesage = _GetWaitLogMessage(logDirectory, waitInSeconds);
std::wstring waitLogMesage = FileMonitorUtilities::_GetWaitLogMessage(logDirectory, waitInSeconds);
logWriter.TraceInfo(waitLogMesage.c_str());
//
@ -70,14 +74,12 @@ HANDLE GetLogDirHandle(_In_ std::wstring logDirectory, _In_ HANDLE stopEvent, _I
logWriter.TraceError(
Utility::FormatString(
L"Failed to create timer object. Log directory %ws will not be monitored for log entries. Error=%d",
logDirectory.c_str(),
status
).c_str()
);
logDirectory.c_str(), status).c_str());
return INVALID_HANDLE_VALUE;
}
logDirHandle = _RetryOpenDirectoryWithInterval(logDirectory, waitInSeconds, stopEvent, timerEvent);
logDirHandle = FileMonitorUtilities::_RetryOpenDirectoryWithInterval(
logDirectory, waitInSeconds, stopEvent, timerEvent);
CancelWaitableTimer(timerEvent);
CloseHandle(timerEvent);
@ -86,7 +88,32 @@ HANDLE GetLogDirHandle(_In_ std::wstring logDirectory, _In_ HANDLE stopEvent, _I
return logDirHandle;
}
HANDLE _RetryOpenDirectoryWithInterval(
void FileMonitorUtilities::ParseDirectoryValue(_Inout_ std::wstring &directory)
{
while (!directory.empty() && directory[directory.size() - 1] == L'\\')
{
directory.resize(directory.size() - 1);
}
}
bool FileMonitorUtilities::IsValidSourceFile(_In_ std::wstring directory, _In_ bool includeSubdirectories)
{
bool isRootFolder = CheckIsRootFolder(directory);
// The source file is invalid if the directory is a root folder and includeSubdirectories = true
// This is because we do not monitor subfolders in the root directory
return !(isRootFolder && includeSubdirectories);
}
bool FileMonitorUtilities::CheckIsRootFolder(_In_ std::wstring dirPath)
{
std::wregex pattern(L"^\\w:?$");
std::wsmatch matches;
return std::regex_search(dirPath, matches, pattern);
}
HANDLE FileMonitorUtilities::_RetryOpenDirectoryWithInterval(
std::wstring logDirectory,
std::double_t waitInSeconds,
HANDLE stopEvent,
@ -99,10 +126,10 @@ HANDLE _RetryOpenDirectoryWithInterval(
const int eventsCount = 2;
HANDLE dirOpenEvents[eventsCount] = {stopEvent, timerEvent};
while (_IsFileErrorStatus(status) && elapsedTime < waitInSeconds)
while (FileMonitorUtilities::_IsFileErrorStatus(status) && elapsedTime < waitInSeconds)
{
int waitInterval = _GetWaitInterval(waitInSeconds, elapsedTime);
LARGE_INTEGER timeToWait = _ConvertWaitIntervalToLargeInt(waitInterval);
int waitInterval = FileMonitorUtilities::_GetWaitInterval(waitInSeconds, elapsedTime);
LARGE_INTEGER timeToWait = FileMonitorUtilities::_ConvertWaitIntervalToLargeInt(waitInterval);
BOOL waitableTimer = SetWaitableTimer(timerEvent, &timeToWait, 0, NULL, NULL, 0);
if (!waitableTimer)
@ -181,7 +208,7 @@ HANDLE _RetryOpenDirectoryWithInterval(
}
// Converts the time to wait to a large integer
LARGE_INTEGER _ConvertWaitIntervalToLargeInt(int timeInterval)
LARGE_INTEGER FileMonitorUtilities::_ConvertWaitIntervalToLargeInt(int timeInterval)
{
LARGE_INTEGER liDueTime{};
@ -191,7 +218,7 @@ LARGE_INTEGER _ConvertWaitIntervalToLargeInt(int timeInterval)
}
// Returns the time (in seconds) to wait based on the specified waitInSeconds
int _GetWaitInterval(std::double_t waitInSeconds, int elapsedTime)
int FileMonitorUtilities::_GetWaitInterval(std::double_t waitInSeconds, int elapsedTime)
{
if (isinf(waitInSeconds))
{
@ -207,12 +234,12 @@ int _GetWaitInterval(std::double_t waitInSeconds, int elapsedTime)
return remainingTime <= WAIT_INTERVAL ? remainingTime : WAIT_INTERVAL;
}
bool _IsFileErrorStatus(DWORD status)
bool FileMonitorUtilities::_IsFileErrorStatus(DWORD status)
{
return status == ERROR_FILE_NOT_FOUND || status == ERROR_PATH_NOT_FOUND;
}
std::wstring _GetWaitLogMessage(_In_ std::wstring logDirectory, _In_ std::double_t waitInSeconds)
std::wstring FileMonitorUtilities::_GetWaitLogMessage(std::wstring logDirectory, std::double_t waitInSeconds)
{
if (isinf(waitInSeconds))
{

Просмотреть файл

@ -0,0 +1,47 @@
//
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
//
#pragma once
class FileMonitorUtilities final
{
public:
static const int WAIT_INTERVAL = 15;
static HANDLE CreateFileMonitorEvent(
_In_ BOOL bManualReset,
_In_ BOOL bInitialState);
static HANDLE GetLogDirHandle(
_In_ std::wstring logDirectory,
_In_ HANDLE stopEvent,
_In_ std::double_t waitInSeconds);
static void ParseDirectoryValue(_Inout_ std::wstring &directory);
static bool IsValidSourceFile(_In_ std::wstring directory, bool includeSubdirectories);
static bool CheckIsRootFolder(_In_ std::wstring dirPath);
private:
static HANDLE _RetryOpenDirectoryWithInterval(
std::wstring logDirectory,
std::double_t waitInSeconds,
HANDLE stopEvent,
HANDLE timerEvent);
static LARGE_INTEGER _ConvertWaitIntervalToLargeInt(
int timeInterval);
static int _GetWaitInterval(
std::double_t waitInSeconds,
int elapsedTime);
static bool _IsFileErrorStatus(DWORD status);
static std::wstring _GetWaitLogMessage(
std::wstring logDirectory,
std::double_t waitInSeconds);
};

Просмотреть файл

@ -1,37 +0,0 @@
//
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
//
#pragma once
const int WAIT_INTERVAL = 15;
HANDLE CreateFileMonitorEvent(
_In_ BOOL bManualReset,
_In_ BOOL bInitialState);
HANDLE GetLogDirHandle(
_In_ std::wstring logDirectory,
_In_ HANDLE stopEvent,
_In_ std::double_t waitInSeconds);
HANDLE _RetryOpenDirectoryWithInterval(
std::wstring logDirectory,
std::double_t waitInSeconds,
HANDLE stopEvent,
HANDLE timerEvent);
LARGE_INTEGER _ConvertWaitIntervalToLargeInt(
int timeInterval);
int _GetWaitInterval(
std::double_t waitInSeconds,
int elapsedTime);
bool _IsFileErrorStatus(
DWORD status);
std::wstring _GetWaitLogMessage(
_In_ std::wstring logDirectory,
_In_ std::double_t waitInSeconds);

Просмотреть файл

@ -4,6 +4,7 @@
//
#include "pch.h"
#include <regex>
using namespace std;
@ -57,26 +58,26 @@ LogFileMonitor::LogFileMonitor(_In_ const std::wstring& LogDirectory,
InitializeSRWLock(&m_eventQueueLock);
while (!m_logDirectory.empty() && m_logDirectory[ m_logDirectory.size() - 1 ] == L'\\')
{
m_logDirectory.resize(m_logDirectory.size() - 1);
}
m_logDirectory = PREFIX_EXTENDED_PATH + m_logDirectory;
// By default, the name is limited to MAX_PATH characters. To extend this limit to 32,767 wide characters,
// we prepend "\?" to the path. Prepending the string "\?" does not allow access to the root directory
// We, therefore, do not prepend for the root directory
bool isRootFolder = CheckIsRootFolder(m_logDirectory);
m_logDirectory = isRootFolder ? m_logDirectory : PREFIX_EXTENDED_PATH + m_logDirectory;
if (m_filter.empty())
{
m_filter = L"*";
}
m_stopEvent = CreateFileMonitorEvent(TRUE, FALSE);
m_stopEvent = FileMonitorUtilities::CreateFileMonitorEvent(TRUE, FALSE);
m_overlappedEvent = CreateFileMonitorEvent(TRUE, TRUE);
m_overlappedEvent = FileMonitorUtilities::CreateFileMonitorEvent(TRUE, TRUE);
m_overlapped.hEvent = m_overlappedEvent;
m_workerThreadEvent = CreateFileMonitorEvent(TRUE, TRUE);
m_workerThreadEvent = FileMonitorUtilities::CreateFileMonitorEvent(TRUE, TRUE);
m_dirMonitorStartedEvent = CreateFileMonitorEvent(TRUE, FALSE);
m_dirMonitorStartedEvent = FileMonitorUtilities::CreateFileMonitorEvent(TRUE, FALSE);
m_readLogFilesFromStart = false;
@ -316,7 +317,7 @@ LogFileMonitor::StartLogFileMonitor()
dirMonitorStartedEventSignalled = true;
// Get Log Dir Handle
HANDLE logDirHandle = GetLogDirHandle(m_logDirectory, m_stopEvent, m_waitInSeconds);
HANDLE logDirHandle = FileMonitorUtilities::GetLogDirHandle(m_logDirectory, m_stopEvent, m_waitInSeconds);
if(logDirHandle == INVALID_HANDLE_VALUE) {
status = GetLastError();
@ -2039,3 +2040,12 @@ LogFileMonitor::GetFileId(
return status;
}
bool
LogFileMonitor::CheckIsRootFolder(_In_ std::wstring dirPath)
{
std::wregex pattern(L"^\\w:?$");
std::wsmatch matches;
return std::regex_search(dirPath, matches, pattern);
}

Просмотреть файл

@ -224,4 +224,6 @@ private:
_Out_ FILE_ID_INFO& FileId,
_In_opt_ HANDLE Handle = INVALID_HANDLE_VALUE
);
static bool CheckIsRootFolder(_In_ std::wstring dirPath);
};

Просмотреть файл

@ -159,7 +159,7 @@
<ItemGroup>
<ClInclude Include="EtwMonitor.h" />
<ClInclude Include="EventMonitor.h" />
<ClInclude Include="FileMonitor\*.h" />
<ClInclude Include="FileMonitor\FileMonitorUtilities.h" />
<ClInclude Include="LogFileMonitor.h" />
<ClInclude Include="LogWriter.h" />
<ClInclude Include="Parser\ConfigFileParser.h" />
@ -176,7 +176,7 @@
<ClCompile Include="EtwMonitor.cpp" />
<ClCompile Include="EventMonitor.cpp" />
<ClCompile Include="JsonFileParser.cpp" />
<ClCompile Include="FileMonitor\*.cpp" />
<ClCompile Include="FileMonitor\FileMonitorUtilities.cpp" />
<ClCompile Include="LogFileMonitor.cpp" />
<ClCompile Include="Main.cpp" />
<ClCompile Include="pch.cpp">

Просмотреть файл

@ -24,7 +24,7 @@
<ClInclude Include="version.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="FileMonitor\*.h">
<ClInclude Include="FileMonitor\FileMonitorUtilities.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="LogFileMonitor.h">
@ -83,7 +83,7 @@
<ClCompile Include="JsonFileParser.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="FileMonitor\*.cpp">
<ClCompile Include="FileMonitor\FileMonitorUtilities.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>

Просмотреть файл

@ -49,4 +49,6 @@ bool AddNewSource(
_Inout_ std::vector<std::shared_ptr<LogSource> >& Sources
);
bool ValidateDirectoryAttributes(_In_ AttributesMap& Attributes);
void _PrintSettings(_Out_ LoggerSettings& Config);

Просмотреть файл

@ -41,14 +41,6 @@
// Define the AttributesMap, that is a map<wstring, void*> with case
// insensitive keys
//
struct CaseInsensitiveWideString
{
bool operator() (const std::wstring& c1, const std::wstring& c2) const {
return _wcsicmp(c1.c_str(), c2.c_str()) < 0;
}
};
typedef std::map<std::wstring, void*, CaseInsensitiveWideString> AttributesMap;
enum class EventChannelLogLevel
{

Просмотреть файл

@ -306,3 +306,9 @@ void Utility::SanitizeJson(_Inout_ std::wstring& str)
i++;
}
}
bool Utility::ConfigAttributeExists(AttributesMap& Attributes, std::wstring attributeName)
{
auto it = Attributes.find(attributeName);
return it != Attributes.end() && it->second != nullptr;
}

Просмотреть файл

@ -5,6 +5,19 @@
#pragma once
//
// Define the AttributesMap, that is a map<wstring, void*> with case
// insensitive keys
//
struct CaseInsensitiveWideString
{
bool operator() (const std::wstring& c1, const std::wstring& c2) const {
return _wcsicmp(c1.c_str(), c2.c_str()) < 0;
}
};
typedef std::map<std::wstring, void*, CaseInsensitiveWideString> AttributesMap;
class Utility final
{
public:
@ -47,5 +60,7 @@ public:
static bool isJsonNumber(_In_ std::wstring& str);
static void SanitizeJson(_Inout_ std::wstring& str);
static void SanitizeJson(_Inout_ std::wstring &str);
static bool ConfigAttributeExists(_In_ AttributesMap &Attributes, _In_ std::wstring attributeName);
};

Просмотреть файл

@ -52,7 +52,7 @@
#include "LogWriter.h"
#include "EtwMonitor.h"
#include "EventMonitor.h"
#include "FileMonitor/Utilities.h"
#include "FileMonitor/FileMonitorUtilities.h"
#include "LogFileMonitor.h"
#include "ProcessMonitor.h"