diff --git a/.gitignore b/.gitignore index 18d42f2..7a3456c 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,4 @@ **/obj/ **/.vs/ **/.vscode/ +**/.github/ \ No newline at end of file diff --git a/README.md b/README.md index b0cb78c..6890b4a 100644 --- a/README.md +++ b/README.md @@ -49,9 +49,9 @@ The tool respects P4CONFIG file usage, as well as supports typical configuration # Build and Test ### Build Requirments: -1. Visual Studio 2022 version 17.5.0 or later -1. Windows SDK version 10.0.26100.1742 -1. Windows WDK version 10.0.26100.2454 +1. Visual Studio 2022 version 17.14.0 or later +1. Windows SDK version 10.0.26100.7175 +1. Windows WDK version 10.0.26100.6584 Details for installing Visual Studio 2022, the Windows Software Development Kit (SDK), and the Windows Driver Kit (WDK) can be found here: > [Download the Windows Driver Kit](https://learn.microsoft.com/en-us/windows-hardware/drivers/download-the-wdk) diff --git a/deploy/pipelines/templates/p4vfs-ci.yml b/deploy/pipelines/templates/p4vfs-ci.yml index d6a713f..92655e6 100644 --- a/deploy/pipelines/templates/p4vfs-ci.yml +++ b/deploy/pipelines/templates/p4vfs-ci.yml @@ -38,14 +38,14 @@ jobs: inputs: restoreSolution: '${{parameters.solutionFile}}' - - task: VSBuild@1 + - task: MSBuild@1 displayName: 'Build' timeoutInMinutes: 180 inputs: - vsVersion: '17.0' solution: '${{parameters.solutionFile}}' - platform: '${{parameters.platform}}' - configuration: '${{parameters.configuration}}' + msbuildVersion: '17.0' + msbuildArchitecture: 'x64' + configuration: ${{parameters.configuration}} msbuildArgs: '${{parameters.additionalBuildArguments}}' - task: PublishSymbols@2 diff --git a/external/OpenSSL/OpenSSL.Module.cs b/external/OpenSSL/OpenSSL.Module.cs index d30a5e5..8de8689 100644 --- a/external/OpenSSL/OpenSSL.Module.cs +++ b/external/OpenSSL/OpenSSL.Module.cs @@ -12,7 +12,7 @@ namespace Microsoft.P4VFS.External { public class OpensslModule : Module { - private const string OPENSSL_VERSION = "3.0.18"; + private const string OPENSSL_VERSION = "3.0.21"; private const string PERL_PACKAGE_NAME = "StrawberryPerl"; private const string PERL_VERSION = "5.28.0.1"; @@ -75,7 +75,7 @@ private void BuildOpensslLibrary(string opensslArchiveFolder, string opensslTarg $"@ECHO ON", $"CALL \"{vcvarsScriptPath}\"", $"CD /D \"{opensslArchiveFolder}\"", - $"\"{perlExe}\" Configure VC-WIN64A no-asm no-tests \"--prefix={opensslConfigurationFolder}\" \"--openssldir={opensslConfigurationFolder}-ssl\" --{configuration}", + $"\"{perlExe}\" Configure VC-WIN64A no-asm no-tests no-makedepend \"--prefix={opensslConfigurationFolder}\" \"--openssldir={opensslConfigurationFolder}-ssl\" --{configuration}", $"IF %ERRORLEVEL% NEQ 0 EXIT /B 1", $"nmake clean", $"IF %ERRORLEVEL% NEQ 0 EXIT /B 1", diff --git a/external/P4API/P4API.Module.cs b/external/P4API/P4API.Module.cs index 9c9df93..9c3204a 100644 --- a/external/P4API/P4API.Module.cs +++ b/external/P4API/P4API.Module.cs @@ -12,7 +12,7 @@ namespace Microsoft.P4VFS.External { public class P4apiModule : Module { - private const string P4API_VERSION = "r25.2"; + private const string P4API_VERSION = "r26.1"; private const string P4API_VISUAL_STUDIO_EDITION = "2022"; public override string Name diff --git a/source/P4VFS.CodeSign/Source/CodeSign.cs b/source/P4VFS.CodeSign/Source/CodeSign.cs index e8c76ce..9078599 100644 --- a/source/P4VFS.CodeSign/Source/CodeSign.cs +++ b/source/P4VFS.CodeSign/Source/CodeSign.cs @@ -15,8 +15,15 @@ namespace Microsoft.P4VFS.CodeSign public interface ICodeSignClient : IDisposable { bool Submit(CodeSignJob job); + CodeSignToken[] CreateTokens(bool interactive); } + public class CodeSignToken + { + public string Name { get; set; } + public string Value { get; set; } + }; + public static class CodeSignInterop { [DllImport("cabapi.dll", EntryPoint = "Cab_Extract", CallingConvention = CallingConvention.Cdecl, SetLastError = true, CharSet = CharSet.Auto)] @@ -32,6 +39,7 @@ public static class CodeSignResources public const string HardwareLabPlaylistXml = "HardwareLabPlaylist.xml"; public const string SignAuthJson = "SignAuth.json"; public const string SignInputSetupJson = "SignInputSetup.json"; + public const string SignInputAttestation = "SignInputAttestation.json"; public const string SignPolicyJson = "SignPolicy.json"; }; diff --git a/source/P4VFS.CodeSign/Source/DevCenterClient.cs b/source/P4VFS.CodeSign/Source/DevCenterClient.cs index 473b5d9..8392417 100644 --- a/source/P4VFS.CodeSign/Source/DevCenterClient.cs +++ b/source/P4VFS.CodeSign/Source/DevCenterClient.cs @@ -25,7 +25,7 @@ namespace Microsoft.P4VFS.CodeSign { public class DevCenterClient : ICodeSignClient { - private static readonly string HdcVersion = "1.0"; + private static readonly string HdcVersion = "2.0"; private static readonly string HdcTenant = "my"; private static readonly string HdcRootUri = $"/v{HdcVersion}/{HdcTenant}/hardware"; @@ -109,6 +109,18 @@ public bool Submit(CodeSignJob job) return true; } + public CodeSignToken[] CreateTokens(bool interactive) + { + JToken hdcSignInput = JObject.Parse(CodeSignUtilities.ExtractResourceToString(CodeSignResources.SignInputAttestation)); + JToken hdcTokenManifest = hdcSignInput.SelectTokens("$.SignBatches[*].SignRequestFiles[*].Manifest").FirstOrDefault(); + CodeSignJob job = null; + + InitializeJob(job, hdcTokenManifest); + CodeSignToken accessToken = new CodeSignToken{ Name = "AccessToken", Value = GetCachedAccessTokenAsync().Result }; + + return new CodeSignToken[]{ accessToken }; + } + public static bool PreprocessSignRequestFile(CodeSignJob job, JToken tokenSignRequestFile, string signFilePath) { JToken tokenManifest = tokenSignRequestFile.SelectToken("$.Manifest"); diff --git a/source/P4VFS.CodeSign/Source/EsrpClient.cs b/source/P4VFS.CodeSign/Source/EsrpClient.cs index 30cf7e0..a910bd9 100644 --- a/source/P4VFS.CodeSign/Source/EsrpClient.cs +++ b/source/P4VFS.CodeSign/Source/EsrpClient.cs @@ -95,6 +95,11 @@ public bool Submit(CodeSignJob job) return esrpClientExitCode == 0; } + public CodeSignToken[] CreateTokens(bool interactive) + { + return null; + } + private string CreateJobSignAuthFile(CodeSignJob job) { return CodeSignUtilities.ExtractResourceToFile(job.TargetFolder, CodeSignResources.SignAuthJson); diff --git a/source/P4VFS.CodeSign/Source/Program.cs b/source/P4VFS.CodeSign/Source/Program.cs index 59ba360..da4cd9b 100644 --- a/source/P4VFS.CodeSign/Source/Program.cs +++ b/source/P4VFS.CodeSign/Source/Program.cs @@ -6,6 +6,7 @@ using System.IO; using System.Security; using System.Reflection; +using System.Text.RegularExpressions; using Microsoft.P4VFS.Extensions; using Microsoft.P4VFS.Extensions.Linq; using Microsoft.P4VFS.CoreInterop; @@ -46,7 +47,19 @@ wait indefinatly until signed binaries arrive. -i Name of input json file resource for signing -c Optional path to the folder containing EsrpClient.exe tool -n Optional path to the folder containing nuget.exe tool if EsrpClient - folder is not specified. This may be an interactive login. + folder is not specified. This may be an interactive login + + + tokens Request and display short-lived access tokens for debugging + service REST APIs + + P4VFS.CodeSign.exe tokens -q -a -i [-s ] + + -q Query available codesign services + -a Request and display tokens for all services + -i Use public interative authentication for codesign service + instead of default SignAuth service principal + -s Request and disply tokens for service by name "; public static int Main(string[] args) @@ -115,6 +128,9 @@ public static int Main(string[] args) case "submit": status = CommandSubmit(cmdArgs); break; + case "tokens": + status = CommandTokens(cmdArgs); + break; default: VirtualFileSystemLog.Error("P4VFS.CodeSign Unknown Command {0}", args[argIndex]); status = false; @@ -197,13 +213,79 @@ private static bool CommandSubmit(string[] args) return true; } + private static bool CommandTokens(string[] args) + { + List serviceNames = new List(); + bool query = false; + bool interactive = false; + + int argIndex = 0; + for (; argIndex < args.Length; ++argIndex) + { + if (String.Compare(args[argIndex], "-s") == 0 && argIndex+1 < args.Length) + serviceNames.Add(args[++argIndex]); + else if (String.Compare(args[argIndex], "-a") == 0) + serviceNames.Add(null); + else if (String.Compare(args[argIndex], "-i") == 0) + interactive = true; + else if (String.Compare(args[argIndex], "-q") == 0) + query = true; + else + break; + } + + if (argIndex < args.Length) + { + VirtualFileSystemLog.Error("Unexpected argument: {0}", args[argIndex]); + return false; + } + + Type[] clientTypes = Assembly.GetExecutingAssembly() + .GetTypes() + .Where(type => type.IsAbstract == false && type.IsClass && typeof(ICodeSignClient).IsAssignableFrom(type)) + .OrderBy(type => type.Name) + .ToArray(); + + Func getServiceName = (Type clientType) => + Regex.Replace(clientType.Name, @"Client$", "", RegexOptions.IgnoreCase); + + if (query) + { + VirtualFileSystemLog.Info("Available codesign services: {0}", String.Join(", ", clientTypes.Select(t => getServiceName(t)))); + return true; + } + + foreach (Type clientType in clientTypes) + { + string serviceName = getServiceName(clientType); + if (serviceNames.Any(name => name == null || String.Equals(name, serviceName, StringComparison.InvariantCultureIgnoreCase))) + { + foreach (CodeSignToken token in CreateCodeSignTokens(clientType, interactive)) + { + VirtualFileSystemLog.Info($"{serviceName}.{token.Name} = {token.Value}\n"); + } + } + } + return true; + } + private static bool SubmitCodeSignJob(CodeSignJob job) where ClientType : ICodeSignClient, new() { + VirtualFileSystemLog.Info("Submitting codesign job for service {0}", typeof(ClientType).Name); using (ICodeSignClient client = new ClientType()) { return client.Submit(job); } } + + private static CodeSignToken[] CreateCodeSignTokens(Type clientType, bool interactive) + { + VirtualFileSystemLog.Info("Creating codesign tokens for service {0}", clientType.Name); + using (ICodeSignClient client = Activator.CreateInstance(clientType) as ICodeSignClient) + { + return client.CreateTokens(interactive) ?? Array.Empty(); + } + } } } diff --git a/source/P4VFS.Console/P4VFS.Notes.txt b/source/P4VFS.Console/P4VFS.Notes.txt index 2219383..142f850 100644 --- a/source/P4VFS.Console/P4VFS.Notes.txt +++ b/source/P4VFS.Console/P4VFS.Notes.txt @@ -1,5 +1,31 @@ Microsoft P4VFS Release Notes +Version [1.30.0.0] +* Updating to P4API 26.1 +* Updating to OpenSSL 3.0.21 as recommended by P4API 26.1 +* Driver security updates to address Microsoft Security Response Center recommendations + MSRC-126533, MSRC-121148, MSRC-121146, MSRC-121144, MSRC-123527, MSRC-121150, MSRC-121128 +* Driver user mode control port messages now require process to have elevated access + for any file operations +* Driver P4VFS_FLT_FILE_HANDLE data shared now indirectly maps to kernel mode objects + to prevent possible tampering in user mode +* Driver user mode control port now uses structured exception handling prevent possible + access violation to user mode input and output buffer. This could be caused by a time-of-check + time-of-use (TOCTOU) race condition. This also prevents possible alignment failures + casting and using misaligned user-mode P4VFS_CONTROL_MSG & P4VFS_CONTROL_REPLY buffers. +* Driver now guarding against invalid offsets in P4VFS_UNICODE_STRING from user mode. +* Driver WPP tracing fixes for unicode file paths with FILE_ID_128 suffix +* Additional driver unit tests for internal P4VFS_FLT_FILE_HANDLE translation +* Addition of TestPush.bat script to optionally use in a post-build deployment to a + virtual machine for full development driver and user-mode testing. +* Updating unit test server to support p4d 2026.1 with default configurable security=4 and + enable database journal to avoid bug with p4d crash when deleting auth extension +* Addition to default ExcludedProcessNames of SenseNdr.exe, SenseDlpProcessor.exe +* OpenSSL configure now includes no-makedepend for a slight reduction in build time +* Fixing info output log when syncing to files that must resolve before submitting +* Addition of CodeSign 'tokens' command to request and display short-lived access tokens + for debugging service REST APIs. + Version [1.29.4.0] * Updating to latest P4API 25.2 * Migrating to OpenSSL 3.0 LTS as recommended by P4API 25.2 with latest 3.0.18 diff --git a/source/P4VFS.Core/Include/SettingManager.h b/source/P4VFS.Core/Include/SettingManager.h index fa2beee..ec6a252 100644 --- a/source/P4VFS.Core/Include/SettingManager.h +++ b/source/P4VFS.Core/Include/SettingManager.h @@ -25,7 +25,7 @@ namespace FileCore { _N( String, SyncResidentPattern, L"" ) \ _N( bool, Unattended, false ) \ _N( String, Verbosity, FileCore::LogChannel::ToString(FileCore::LogChannel::Info).c_str() ) \ - _N( String, ExcludedProcessNames, L"MsSense.exe;MsMpEng.exe;SenseCE.exe;SenseIR.exe;SearchProtocolHost.exe;MpDlpService.exe" ) \ + _N( String, ExcludedProcessNames, L"MsSense.exe;MsMpEng.exe;SenseCE.exe;SenseIR.exe;SearchProtocolHost.exe;MpDlpService.exe;SenseNdr.exe;SenseDlpProcessor.exe" ) \ _N( int32_t, CreateFileRetryCount, 8 ) \ _N( int32_t, CreateFileRetryWaitMs, 250 ) \ _N( int32_t, PoolDefaultNumberOfThreads, 8 ) \ diff --git a/source/P4VFS.Core/P4VFS.Core.vcxproj b/source/P4VFS.Core/P4VFS.Core.vcxproj index 1b6e362..1daaeef 100644 --- a/source/P4VFS.Core/P4VFS.Core.vcxproj +++ b/source/P4VFS.Core/P4VFS.Core.vcxproj @@ -149,7 +149,7 @@ DebugFull - advapi32.lib;bcrypt.lib;ws2_32.lib;wtsapi32.lib;shlwapi.lib;shell32.lib;fltlib.lib;userenv.lib;crypt32.lib;$(PerforceApiLibFiles);$(OpenSSLApiLibFiles);%(AdditionalDependencies) + advapi32.lib;bcrypt.lib;ws2_32.lib;wtsapi32.lib;shlwapi.lib;shell32.lib;fltlib.lib;userenv.lib;crypt32.lib;iphlpapi.lib;$(PerforceApiLibFiles);$(OpenSSLApiLibFiles);%(AdditionalDependencies) Default $(PerforceApiLibDir);$(OpenSSLApiLibDir);%(AdditionalLibraryDirectories) @@ -177,7 +177,7 @@ DebugFull - advapi32.lib;bcrypt.lib;ws2_32.lib;wtsapi32.lib;shlwapi.lib;shell32.lib;fltlib.lib;userenv.lib;crypt32.lib;$(PerforceApiLibFiles);$(OpenSSLApiLibFiles);%(AdditionalDependencies) + advapi32.lib;bcrypt.lib;ws2_32.lib;wtsapi32.lib;shlwapi.lib;shell32.lib;fltlib.lib;userenv.lib;crypt32.lib;iphlpapi.lib;$(PerforceApiLibFiles);$(OpenSSLApiLibFiles);%(AdditionalDependencies) UseLinkTimeCodeGeneration $(PerforceApiLibDir);$(OpenSSLApiLibDir);%(AdditionalLibraryDirectories) diff --git a/source/P4VFS.Core/Source/DepotSyncAction.cpp b/source/P4VFS.Core/Source/DepotSyncAction.cpp index cbb6e5f..8581bce 100644 --- a/source/P4VFS.Core/Source/DepotSyncAction.cpp +++ b/source/P4VFS.Core/Source/DepotSyncAction.cpp @@ -220,7 +220,8 @@ DepotSyncActionInfo FDepotSyncActionInfo::FromInfoOutput(const DepotString& info info->m_ClientFile = match[1]; } } - else if (std::regex_search(infoText.c_str(), match, rx.m_ActionNeedsResolve)) + + if (std::regex_search(infoText.c_str(), match, rx.m_ActionNeedsResolve)) { info->m_DepotFile = match[2]; info->m_Revision = FDepotRevision::FromString(match[4]); diff --git a/source/P4VFS.Core/Tests/TestDriver.cpp b/source/P4VFS.Core/Tests/TestDriver.cpp index 7391e73..bc6a048 100644 --- a/source/P4VFS.Core/Tests/TestDriver.cpp +++ b/source/P4VFS.Core/Tests/TestDriver.cpp @@ -243,18 +243,21 @@ typedef enum _POOL_TYPE { #define max(a,b) (((a) > (b)) ? (a) : (b)) #define RtlUShortAdd UShortAdd -#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0) -#define STATUS_SUCCESS ((NTSTATUS)0x00000000L) -#define STATUS_UNSUCCESSFUL ((NTSTATUS)0xC0000001L) -#define STATUS_BUFFER_OVERFLOW ((NTSTATUS)0x80000005L) -#define STATUS_BUFFER_TOO_SMALL ((NTSTATUS)0xC0000023L) -#define STATUS_PORT_DISCONNECTED ((NTSTATUS)0xC0000037L) -#define STATUS_DATA_ERROR ((NTSTATUS)0xC000003EL) -#define STATUS_INVALID_PORT_HANDLE ((NTSTATUS)0xC0000042L) -#define STATUS_INSUFFICIENT_RESOURCES ((NTSTATUS)0xC000009AL) -#define STATUS_MEMORY_NOT_ALLOCATED ((NTSTATUS)0xC00000A0L) -#define STATUS_NOT_ALL_ASSIGNED ((NTSTATUS)0x00000106L) -#define STATUS_INVALID_BUFFER_SIZE ((NTSTATUS)0xC0000206L) +#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0) +#define STATUS_SUCCESS ((NTSTATUS)0x00000000L) +#define STATUS_UNSUCCESSFUL ((NTSTATUS)0xC0000001L) +#define STATUS_BUFFER_OVERFLOW ((NTSTATUS)0x80000005L) +#define STATUS_BUFFER_TOO_SMALL ((NTSTATUS)0xC0000023L) +#define STATUS_PORT_DISCONNECTED ((NTSTATUS)0xC0000037L) +#define STATUS_DATA_ERROR ((NTSTATUS)0xC000003EL) +#define STATUS_INVALID_PORT_HANDLE ((NTSTATUS)0xC0000042L) +#define STATUS_INSUFFICIENT_RESOURCES ((NTSTATUS)0xC000009AL) +#define STATUS_MEMORY_NOT_ALLOCATED ((NTSTATUS)0xC00000A0L) +#define STATUS_NOT_ALL_ASSIGNED ((NTSTATUS)0x00000106L) +#define STATUS_INVALID_BUFFER_SIZE ((NTSTATUS)0xC0000206L) +#define STATUS_NOT_FOUND ((NTSTATUS)0xC0000225L) +#define STATUS_INVALID_ADDRESS ((NTSTATUS)0xC0000141L) +#define STATUS_INVALID_OFFSET_ALIGNMENT ((NTSTATUS)0xC0000474L) #define IO_IGNORE_SHARE_ACCESS_CHECK 0x0800 #define FILE_SHARE_VALID_FLAGS 0x00000007 @@ -263,6 +266,7 @@ typedef enum _POOL_TYPE { #define FLT_FILE_NAME_ALLOW_QUERY_ON_REPARSE 0x04000000 #define OBJ_CASE_INSENSITIVE 0x00000040L #define OBJ_KERNEL_HANDLE 0x00000200L +#define OBJ_FORCE_ACCESS_CHECK 0x00000400L #define POOL_FLAG_NON_PAGED 0x0000000000000040UI64 #define FileBasicInformation 4 #define FileStandardInformation 5 @@ -278,7 +282,8 @@ typedef enum _POOL_TYPE { #define P4VFS_DEFAULT_ACTION() [](...) -> VOID { } #define P4VFS_DEFAULT_FUNCTION(retType, retValue) [](...) -> retType { return retValue; } -#define P4VFS_DEFAULT_FUNCTION_NTSTATUS() P4VFS_DEFAULT_FUNCTION(NTSTATUS, STATUS_UNSUCCESSFUL) +#define P4VFS_DEFAULT_FUNCTION_NTSTATUS() [](...) -> NTSTATUS { return STATUS_UNSUCCESSFUL; } +#define P4VFS_UNIMPLEMENTED_FUNCTION_NTSTATUS() [](...) -> NTSTATUS { Assert(false); return STATUS_UNSUCCESSFUL; } std::function FltGetFileNameInformation; std::function FltParseFileNameInformation; @@ -301,6 +306,8 @@ std::function ExAllocatePoolZero; std::function ExFreePoolWithTag; std::function ExAcquireFastMutex; std::function ExReleaseFastMutex; +std::function ProbeForRead; +std::function ProbeForWrite; NTSTATUS RtlAppendUnicodeToString(PUNICODE_STRING dst, PCWSTR src) { @@ -417,6 +424,13 @@ NTSTATUS SeQueryInformationToken(PACCESS_TOKEN token, TOKEN_INFORMATION_CLASS to return STATUS_UNSUCCESSFUL; } +LARGE_INTEGER KeQueryPerformanceCounter(PLARGE_INTEGER*) +{ + LARGE_INTEGER counter = {0}; + QueryPerformanceCounter(&counter); + return counter; +} + #include "DriverCore.h" #include "DriverCore.c" @@ -451,10 +465,12 @@ static void InternalTestDriverReset(const TestContext& context) FltGetVolumeFromFileObject = P4VFS_DEFAULT_FUNCTION_NTSTATUS(); FltGetVolumeInstanceFromName = P4VFS_DEFAULT_FUNCTION_NTSTATUS(); - ExAllocatePoolZero = [](POOL_TYPE, SIZE_T s, ULONG) -> PVOID { return GAlloc(s); }; + ExAllocatePoolZero = [](POOL_TYPE, SIZE_T s, ULONG) -> PVOID { void* p = GAlloc(s); Assert(p); ZeroMemory(p, s); return p; }; ExFreePoolWithTag = [](PVOID p, ULONG) -> VOID { GFree(p); }; ExAcquireFastMutex = P4VFS_DEFAULT_ACTION(); ExReleaseFastMutex = P4VFS_DEFAULT_ACTION(); + ProbeForRead = P4VFS_UNIMPLEMENTED_FUNCTION_NTSTATUS(); + ProbeForWrite = P4VFS_UNIMPLEMENTED_FUNCTION_NTSTATUS(); }; static UNICODE_STRING CStrToUnicodeString(const WCHAR* text) @@ -471,28 +487,84 @@ static UNICODE_STRING CStrToUnicodeString(const WCHAR* text) void TestDriverUnicodeString(const TestContext& context) { - const UCHAR sentinalByte = 0xCD; const WCHAR* shortFilePath = L"C:\\memory.dmp"; const WCHAR* typicalFilePath = L"C:\\depot\\tools\\dev\\source\\Hammer\\Hammer.Interfaces\\BaseClasses\\Launcher\\ApplicationDescription.cs"; const WCHAR* veryLongFilePath = L"C:\\depot\\tools\\dev\\external\\packages\\thirdparty\\microsoft\\Windows Kits\\10\\References\\10.0.17134.0\\Windows.ApplicationModel.Activation.WebUISearchActivatedEventsContract\\1.0.0.0\\zh-hans\\2019\\Enterprise\\VSSDK\\VisualStudioIntegration\\Common\\Source\\CPP\\VSL\\VSLArcitecture_files\\Microsoft Azure Tools\\Visual Studio 16.0\\2.9\\RemoteDebuggerConnector\\Connector\\MSBuild\\Microsoft\\Microsoft.NET.Build.Extensions\\net471\\Windows.ApplicationModel.Activation.WebUISearchActivatedEventsContract\\References\\Windows.ApplicationModel.CommunicationBlocking.CommunicationBlockingContract\\2.0.0.0\\Windows.ApplicationModel.CommunicationBlocking.CommunicationBlockingContract.WinMD\\Windows.ApplicationModel.Background.BackgroundAlarmApplicationContract.xml"; - auto AssertCopyAssignUnicodeString = [&](const WCHAR* srcText, const ULONG dstPadding = 0, const LONG srcLength = -1) -> void + auto AssertCopyAssignUnicodeString = [&context](const WCHAR* srcText, const ULONG dstPadding = 0, const LONG srcLength = -1) -> void { InternalTestDriverReset(context); + + const UCHAR sentinalByte = 0xCD; const LONG srcTextLength = srcLength < 0 ? LONG(StringInfo::Strlen(srcText)) : srcLength; + const ULONG largeTextOffset = max(srcTextLength * 8, 1024); const ULONG srcTextSizeBytes = ULONG(srcTextLength*sizeof(WCHAR)); const ULONG dstTextSizeBytes = srcTextSizeBytes+sizeof(WCHAR); + Array dstBuffer(sizeof(P4VFS_UNICODE_STRING)+dstPadding+dstTextSizeBytes+sizeof(sentinalByte), 0); dstBuffer[dstBuffer.size()-sizeof(sentinalByte)] = sentinalByte; P4VFS_UNICODE_STRING* dstString = (P4VFS_UNICODE_STRING*)dstBuffer.data(); + // Serialize the srcText into a variable length P4VFS_UNICODE_STRING starting dstPadding past the header Assert(P4vfsCopyAssignUnicodeString(dstString, dstBuffer.data()+sizeof(P4VFS_UNICODE_STRING)+dstPadding, dstTextSizeBytes, srcText, srcTextSizeBytes) == STATUS_SUCCESS); + // Verify the P4VFS_UNICODE_STRING reflects the srcText we expect Assert(StringInfo::Strncmp(dstString->c_str(), srcText, srcTextLength) == 0); Assert(StringInfo::Strlen(dstString->c_str()) == srcTextLength); Assert(dstString->c_str()[srcTextLength] == L'\0'); Assert(dstString->sizeBytes == dstTextSizeBytes); Assert(dstBuffer[dstBuffer.size()-1] == sentinalByte); + const P4VFS_UNICODE_STRING dstValidString = *dstString; + + // Test reading a P4VFS_UNICODE_STRING from an arbitrary buffer for goodness or badness! + auto AssertToUnicodeString = [srcText, srcTextLength](const P4VFS_UNICODE_STRING* dataString, const void* dataBuffer, size_t dataBufferLength, NTSTATUS expectStatus) -> void + { + UNICODE_STRING kmString = {0}; + Assert(P4vfsToUnicodeString(dataString, &kmString, dataBuffer, (ULONG)dataBufferLength) == expectStatus); + if (expectStatus == STATUS_SUCCESS) + { + Assert(kmString.Length == srcTextLength*sizeof(WCHAR)); + Assert(kmString.Buffer && memcmp(kmString.Buffer, srcText, srcTextLength*sizeof(WCHAR)) == 0); + } + }; + + // Test reading a P4VFS_UNICODE_STRING with good or bad buffer ranges + AssertToUnicodeString(dstString, dstBuffer.data(), dstBuffer.size(), STATUS_SUCCESS); + AssertToUnicodeString(dstString, dstBuffer.data()-largeTextOffset, dstBuffer.size(), STATUS_INVALID_ADDRESS); + AssertToUnicodeString(dstString, dstBuffer.data()-largeTextOffset, dstBuffer.size()+largeTextOffset, STATUS_SUCCESS); + AssertToUnicodeString(dstString, dstBuffer.data()+largeTextOffset, dstBuffer.size(), STATUS_INVALID_ADDRESS); + + // Testing reading when header buffer is outside data buffer + P4VFS_UNICODE_STRING dstStackString = *dstString; + AssertToUnicodeString(&dstStackString, dstBuffer.data(), dstBuffer.size(), STATUS_INVALID_ADDRESS); + + // Test when string size overflows the data buffer + dstString->sizeBytes = largeTextOffset; + AssertToUnicodeString(dstString, dstBuffer.data(), dstBuffer.size(), STATUS_INVALID_OFFSET_ALIGNMENT); + *dstString = dstValidString; + + // Test when string data size is not 2 byte aligned, as expected for WCHAR string + dstString->sizeBytes += 1; + AssertToUnicodeString(dstString, dstBuffer.data(), dstBuffer.size(), STATUS_DATATYPE_MISALIGNMENT); + *dstString = dstValidString; + + // Test when offset to string payload overflows the data buffer + dstString->offsetBytes = largeTextOffset; + AssertToUnicodeString(dstString, dstBuffer.data(), dstBuffer.size(), STATUS_INVALID_OFFSET_ALIGNMENT); + *dstString = dstValidString; + + // Test when offset to string payload underflows the data buffer + dstString->offsetBytes = -1 * LONG(largeTextOffset); + AssertToUnicodeString(dstString, dstBuffer.data(), dstBuffer.size(), STATUS_INVALID_OFFSET_ALIGNMENT); + *dstString = dstValidString; + + // Test when offset to string payload overflows the data buffer by maximum possible, confirming pointer arithmatic safety + dstString->offsetBytes = LONG_MAX; + AssertToUnicodeString(dstString, dstBuffer.data(), dstBuffer.size(), STATUS_INVALID_OFFSET_ALIGNMENT); + *dstString = dstValidString; + + // Test again to confirm our previous tests didn't fail for some other reason + AssertToUnicodeString(dstString, dstBuffer.data(), dstBuffer.size(), STATUS_SUCCESS); }; AssertCopyAssignUnicodeString(typicalFilePath); @@ -544,3 +616,50 @@ void TestDriverUnicodeString(const TestContext& context) AssertUserModeResolveFile(veryLongFilePath, L"C:\\"); } +void TestDriverOpenFileObjectList(const TestContext& context) +{ + auto MakeFileObject = [](const VOID* fileObject, const VOID* fileHandle) -> P4VFS_OPEN_FILE_OBJECT + { + P4VFS_OPEN_FILE_OBJECT obj = {0}; + obj.pFileObject = (PFILE_OBJECT)fileObject; + obj.fltFileHandle.fileHandle = (PVOID)fileHandle; + return obj; + }; + + InternalTestDriverReset(context); + Assert(g_FltContext.pOpenFileObjectList == NULL); + + const P4VFS_OPEN_FILE_OBJECT src0 = MakeFileObject(L"fo0", L"fh0"); + P4VFS_OPEN_FILE_OBJECT push0 = {0}; + Assert(P4vfsPushOpenFileObject(src0.pFileObject, src0.fltFileHandle.fileHandle, &push0) == STATUS_SUCCESS); + Assert(src0.pFileObject == push0.pFileObject && src0.fltFileHandle.fileHandle == push0.fltFileHandle.fileHandle && push0.fltFileHandle.fileId.data != 0 && push0.pNext == NULL); + + Assert(g_FltContext.pOpenFileObjectList != NULL); + + const P4VFS_OPEN_FILE_OBJECT src1 = MakeFileObject(L"fo1", L"fh1"); + P4VFS_OPEN_FILE_OBJECT push1 = {0}; + Assert(P4vfsPushOpenFileObject(src1.pFileObject, src1.fltFileHandle.fileHandle, &push1) == STATUS_SUCCESS); + Assert(src1.pFileObject == push1.pFileObject && src1.fltFileHandle.fileHandle == push1.fltFileHandle.fileHandle && push1.fltFileHandle.fileId.data != 0 && push1.pNext == NULL); + Assert(push1.fltFileHandle.fileId.data != push0.fltFileHandle.fileId.data); + + P4VFS_OPEN_FILE_OBJECT pop1 = {0}; + Assert(P4vfsPopOpenFileObject(&push1.fltFileHandle, &pop1) == STATUS_SUCCESS); + Assert(src1.pFileObject == pop1.pFileObject && src1.fltFileHandle.fileHandle == pop1.fltFileHandle.fileHandle && push1.fltFileHandle.fileId.data == pop1.fltFileHandle.fileId.data && pop1.pNext == NULL); + + P4VFS_OPEN_FILE_OBJECT push2 = {0}; + Assert(P4vfsPushOpenFileObject(src0.pFileObject, src0.fltFileHandle.fileHandle, &push2) == STATUS_SUCCESS); + Assert(src0.pFileObject == push2.pFileObject && src0.fltFileHandle.fileHandle == push2.fltFileHandle.fileHandle && push2.fltFileHandle.fileId.data != 0 && push2.pNext == NULL); + + P4VFS_OPEN_FILE_OBJECT pop0 = {0}; + Assert(P4vfsPopOpenFileObject(&push0.fltFileHandle, &pop0) == STATUS_SUCCESS); + Assert(pop0.pFileObject == push0.pFileObject && pop0.fltFileHandle.fileHandle == push0.fltFileHandle.fileHandle && pop0.fltFileHandle.fileId.data == push0.fltFileHandle.fileId.data && pop0.pNext == NULL); + + P4VFS_OPEN_FILE_OBJECT pop3 = {0}; + Assert(P4vfsPopOpenFileObject(&push0.fltFileHandle, &pop3) == STATUS_NOT_FOUND); + + P4VFS_OPEN_FILE_OBJECT pop2 = {0}; + Assert(P4vfsPopOpenFileObject(&push2.fltFileHandle, &pop2) == STATUS_SUCCESS); + Assert(pop2.pFileObject == push2.pFileObject && pop2.fltFileHandle.fileHandle == push2.fltFileHandle.fileHandle && pop2.fltFileHandle.fileId.data != 0 && pop2.pNext == NULL); + + Assert(g_FltContext.pOpenFileObjectList == NULL); +} diff --git a/source/P4VFS.Core/Tests/TestFileOperations.cpp b/source/P4VFS.Core/Tests/TestFileOperations.cpp index 0fb31c9..1e96494 100644 --- a/source/P4VFS.Core/Tests/TestFileOperations.cpp +++ b/source/P4VFS.Core/Tests/TestFileOperations.cpp @@ -65,7 +65,7 @@ void TestFileOperationsOpenReparsePointFile(const TestContext& context) // Open the file for write as a reparse point excluding share access checks P4VFS_FLT_FILE_HANDLE fltFile = FileOperations::OpenReparsePointFile(reparseFilePath.c_str(), GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE); - Assert(fltFile.fileHandle != NULL && fltFile.fileHandle != INVALID_HANDLE_VALUE && fltFile.fileObject != nullptr); + Assert(fltFile.fileHandle != NULL && fltFile.fileHandle != INVALID_HANDLE_VALUE && fltFile.fileId.data != 0); Array localFileWriteBytes; for (uint32_t i = 1; i <= 5693; ++i) @@ -136,12 +136,12 @@ void TestFileOperationsAccess(const TestContext& context) AssertTypeLogFile(testElevatedOutputFile, TEXT("[AccessElevated] ")); Assert(testElevatedResult == 0); - // Confirm successfull run of p4vfs test TestFileOperationsAccessUnelevated + // Confirm unsuccessfull run of p4vfs test TestFileOperationsAccessUnelevated const String testUnelevatedOutputFile = FileInfo::FullPath(StringInfo::Format(TEXT("%s\\test-unelevated.txt"), localRootFolder.c_str()).c_str()); int32_t testUnelevatedPriority = P4VFS_FIND_TEST(TestFileOperationsAccessUnelevated).m_Priority; int32_t testUnelevatedResult = TestUtilities::ExecuteWait(context, StringInfo::Format(TEXT("cmd.exe /s /c %s test -e %d > \"%s\" 2>&1"), p4vfsExe.c_str(), testUnelevatedPriority, testUnelevatedOutputFile.c_str()), nullptr, Process::ExecuteFlags::Unelevated); AssertTypeLogFile(testUnelevatedOutputFile, TEXT("[AccessUnelevated] ")); - Assert(testUnelevatedResult == 0); + Assert(testUnelevatedResult == 1); } void AssertFileOperationsAccessInternal(const TestContext& context, bool isElevated) @@ -149,11 +149,11 @@ void AssertFileOperationsAccessInternal(const TestContext& context, bool isEleva // Confirm successfull run of elevated process if expected Assert((TestUtilities::ExecuteWait(context, TEXT("fltmc.exe")) == 0) == isElevated); - // Attempt to open read/write an an existing file under an elevation protected folder. In the future we may want to restrict this to isElevated only + // Attempt to open read/write an an existing file under an elevation protected folder. This is restricted to elevated only const String adminFilePath = FileOperations::GetExpandedEnvironmentStrings(TEXT("%ProgramFiles%\\P4VFS\\P4VFS.Notes.txt")); Assert(FileInfo::IsRegular(adminFilePath.c_str())); P4VFS_FLT_FILE_HANDLE adminFileHandle = FileOperations::OpenReparsePointFile(adminFilePath.c_str(), FILE_GENERIC_READ|FILE_GENERIC_WRITE, 0); - Assert(adminFileHandle.fileHandle != NULL && adminFileHandle.fileHandle != INVALID_HANDLE_VALUE && adminFileHandle.fileObject != nullptr); + Assert(adminFileHandle.fileHandle != NULL && adminFileHandle.fileHandle != INVALID_HANDLE_VALUE && adminFileHandle.fileId.data != 0); Assert(SUCCEEDED(FileOperations::CloseReparsePointFile(adminFileHandle))); // Attempt to set a driver control message which should be elevated only (this should be a proper set and restore) diff --git a/source/P4VFS.Core/Tests/TestRegistry.cpp b/source/P4VFS.Core/Tests/TestRegistry.cpp index 87b14eb..43553ab 100644 --- a/source/P4VFS.Core/Tests/TestRegistry.cpp +++ b/source/P4VFS.Core/Tests/TestRegistry.cpp @@ -55,6 +55,7 @@ P4VFS_REGISTER_TEST( TestThreadPool, 10800 ) // TestDriver P4VFS_REGISTER_TEST( TestDriverUnicodeString, 10900 ) +P4VFS_REGISTER_TEST( TestDriverOpenFileObjectList, 10901 ) // TestFileOperations P4VFS_REGISTER_TEST( TestFileOperationsUnicodeString, 11000 ) diff --git a/source/P4VFS.CoreInterop/P4VFS.CoreInterop.vcxproj b/source/P4VFS.CoreInterop/P4VFS.CoreInterop.vcxproj index 4c0a90a..4147484 100644 --- a/source/P4VFS.CoreInterop/P4VFS.CoreInterop.vcxproj +++ b/source/P4VFS.CoreInterop/P4VFS.CoreInterop.vcxproj @@ -113,7 +113,7 @@ DebugFull - $(P4VFSCoreName).lib;advapi32.lib;bcrypt.lib;ws2_32.lib;wtsapi32.lib;shlwapi.lib;shell32.lib;fltlib.lib;userenv.lib;crypt32.lib;$(PerforceApiLibFiles);$(OpenSSLApiLibFiles) + $(P4VFSCoreName).lib;advapi32.lib;bcrypt.lib;ws2_32.lib;wtsapi32.lib;shlwapi.lib;shell32.lib;fltlib.lib;userenv.lib;crypt32.lib;iphlpapi.lib;$(PerforceApiLibFiles);$(OpenSSLApiLibFiles) Default $(P4VFSCoreBuildDir)\$(Configuration);$(PerforceApiLibDir);$(OpenSSLApiLibDir);%(AdditionalLibraryDirectories) @@ -141,7 +141,7 @@ DebugFull - $(P4VFSCoreName).lib;advapi32.lib;bcrypt.lib;ws2_32.lib;wtsapi32.lib;shlwapi.lib;shell32.lib;fltlib.lib;userenv.lib;crypt32.lib;$(PerforceApiLibFiles);$(OpenSSLApiLibFiles) + $(P4VFSCoreName).lib;advapi32.lib;bcrypt.lib;ws2_32.lib;wtsapi32.lib;shlwapi.lib;shell32.lib;fltlib.lib;userenv.lib;crypt32.lib;iphlpapi.lib;$(PerforceApiLibFiles);$(OpenSSLApiLibFiles) Default $(P4VFSCoreBuildDir)\$(Configuration);$(PerforceApiLibDir);$(OpenSSLApiLibDir);%(AdditionalLibraryDirectories) diff --git a/source/P4VFS.CoreInterop/Source/Pch.h b/source/P4VFS.CoreInterop/Source/Pch.h index bff2b3a..aec0a0c 100644 --- a/source/P4VFS.CoreInterop/Source/Pch.h +++ b/source/P4VFS.CoreInterop/Source/Pch.h @@ -26,6 +26,7 @@ #include #include #include +#include #include #include diff --git a/source/P4VFS.Driver/Include/DriverCore.h b/source/P4VFS.Driver/Include/DriverCore.h index e0e6244..80bb5a4 100644 --- a/source/P4VFS.Driver/Include/DriverCore.h +++ b/source/P4VFS.Driver/Include/DriverCore.h @@ -13,6 +13,9 @@ #define P4VFS_FILE_NAME_ALLOC_TAG 'NsvP' #define P4VFS_SERVICE_PORT_HANDLE_ALLOC_TAG 'SsvP' #define P4VFS_CONTROL_PORT_HANDLE_ALLOC_TAG 'CsvP' +#define P4VFS_OPEN_FILE_OBJECT_ALLOC_TAG 'FsvP' +#define P4VFS_CONTROL_MSG_ALLOC_TAG 'MsvP' +#define P4VFS_CONTROL_REPLY_ALLOC_TAG 'RsvP' NTSTATUS P4vfsUserModeExecuteDrvRequest( @@ -97,7 +100,7 @@ P4vfsCopyAssignUnicodeString( _In_ P4VFS_UNICODE_STRING* pTargetString, _In_ VOID* pTargetBuffer, _In_ ULONG targetBufferSize, - _In_ const VOID* pSourceBuffer, + _In_ CONST VOID* pSourceBuffer, _In_ ULONG sourceBufferSize ); @@ -109,8 +112,10 @@ P4vfsAllocateUnicodeString( NTSTATUS P4vfsToUnicodeString( - _In_ const P4VFS_UNICODE_STRING* pSrcString, - _Out_ UNICODE_STRING* pDstString + _In_ CONST P4VFS_UNICODE_STRING* pSrcString, + _Out_ UNICODE_STRING* pDstString, + _In_ CONST VOID* pSrcBuffer, + _In_ CONST ULONG dwSrcBufferLength ); NTSTATUS @@ -126,21 +131,46 @@ P4vfsGetFileIdByFileName( _Outptr_opt_ PFLT_INSTANCE* ppFltInstance ); +NTSTATUS +P4vfsPushOpenFileObject( + _In_ PFILE_OBJECT pFileObject, + _In_ HANDLE fileHandle, + _Out_ P4VFS_OPEN_FILE_OBJECT* pOpenFileObject + ); + +NTSTATUS +P4vfsPopOpenFileObject( + _In_ P4VFS_FLT_FILE_HANDLE* pFileHandle, + _Out_ P4VFS_OPEN_FILE_OBJECT* pOpenFileObject + ); + NTSTATUS P4vfsOpenReparsePoint( _In_ PUNICODE_STRING pFileName, _In_ ACCESS_MASK desiredAccess, - _Out_ PHANDLE pTargetHandle, - _Outptr_ PFILE_OBJECT* ppTargetFileObject + _Out_ P4VFS_FLT_FILE_HANDLE* pFileHandle ); NTSTATUS P4vfsCloseReparsePoint( - _In_ HANDLE hHandle, - _In_ PFILE_OBJECT pFileObject + _In_ P4VFS_FLT_FILE_HANDLE* pFileHandle ); BOOLEAN P4vfsIsCurrentProcessElevated( ); +NTSTATUS +P4vfsReadUserMemory( + _Out_ PVOID pTargetBuffer, + _In_ CONST VOID* pUserSourceBuffer, + _In_ ULONG dwLength + ); + +NTSTATUS +P4vfsWriteUserMemory( + _Out_ PVOID pUserTargetBuffer, + _In_ CONST VOID* pSourceBuffer, + _In_ ULONG dwLength + ); + diff --git a/source/P4VFS.Driver/Include/DriverData.h b/source/P4VFS.Driver/Include/DriverData.h index 8861f19..9446fa9 100644 --- a/source/P4VFS.Driver/Include/DriverData.h +++ b/source/P4VFS.Driver/Include/DriverData.h @@ -40,6 +40,7 @@ #define P4VFS_CONTROL_PORT_NAME L"\\P4VFS_CONTROL_PORT_NAME" #define P4VFS_CONTROL_FLAG_LENGTH 32 +#define P4VFS_CONTROL_MAX_BUFFER_LENGTH USHORT_MAX typedef struct _P4VFS_UNICODE_STRING { @@ -135,10 +136,15 @@ typedef struct _P4VFS_SERVICE_REPLY typedef struct _FILE_OBJECT* PFILE_OBJECT; #endif +typedef struct _P4VFS_FLT_FILE_ID +{ + ULONGLONG data; +} P4VFS_FLT_FILE_ID; + typedef struct _P4VFS_FLT_FILE_HANDLE { HANDLE fileHandle; - PFILE_OBJECT fileObject; + P4VFS_FLT_FILE_ID fileId; } P4VFS_FLT_FILE_HANDLE; typedef struct _P4VFS_CONTROL_MSG diff --git a/source/P4VFS.Driver/Include/DriverFilter.h b/source/P4VFS.Driver/Include/DriverFilter.h index 84093ed..b9e97d9 100644 --- a/source/P4VFS.Driver/Include/DriverFilter.h +++ b/source/P4VFS.Driver/Include/DriverFilter.h @@ -12,6 +12,13 @@ typedef struct _P4VFS_REPARSE_ACTION LONG nRefCount; } P4VFS_REPARSE_ACTION; +typedef struct _P4VFS_OPEN_FILE_OBJECT +{ + struct _P4VFS_OPEN_FILE_OBJECT* pNext; + P4VFS_FLT_FILE_HANDLE fltFileHandle; + PFILE_OBJECT pFileObject; +} P4VFS_OPEN_FILE_OBJECT; + typedef struct _P4VFS_FLT_CONTEXT { DRIVER_OBJECT* pDriverObject; // The object that IDs the driver @@ -19,11 +26,14 @@ typedef struct _P4VFS_FLT_CONTEXT PFLT_PORT pServiceServerPort; // Filter service listening port PFLT_PORT pServiceClientPort; // Latest active service port PFLT_PORT pControlServerPort; // Filter control listening port - LONG nRequestCount; // Number of requests processed + LONG nRequestCount; // Monotonic increasing number of requests processed + LONG nFileIdCount; // Monotonic increasing number of P4VFS_FLT_FILE_ID's created BOOLEAN bSanitizeAttributes; // Enable stripping reparse and sparse file attributes BOOLEAN bShareModeDuringHydration; // Force file handle creation during hydration have share mode (legacy requirement) - P4VFS_REPARSE_ACTION* pReparseActionList; - FAST_MUTEX hReparseActionLock; + P4VFS_REPARSE_ACTION* pReparseActionList; // Linked list of reparse actions in progress + FAST_MUTEX hReparseActionLock; // Mutex for exclusive access to pReparseActionList + P4VFS_OPEN_FILE_OBJECT* pOpenFileObjectList; // Linked list of open file objects from P4vfsOpenReparsePoint + FAST_MUTEX hOpenFileObjectLock; // Mutex for exclusive access to pOpenFileObjectList } P4VFS_FLT_CONTEXT; extern P4VFS_FLT_CONTEXT g_FltContext; diff --git a/source/P4VFS.Driver/Include/DriverTrace.h b/source/P4VFS.Driver/Include/DriverTrace.h index 4579479..18abb4f 100644 --- a/source/P4VFS.Driver/Include/DriverTrace.h +++ b/source/P4VFS.Driver/Include/DriverTrace.h @@ -5,20 +5,55 @@ #define P4VFS_WPP_CONTROL_GUID \ 082E7434-2FF0-4DE7-8470-1BBBD2E48237 -#define WPP_CONTROL_GUIDS \ - WPP_DEFINE_CONTROL_GUID( \ - P4VFS_DRIVER_TRACE_GUID, (082E7434, 2FF0, 4DE7, 8470, 1BBBD2E48237), \ - WPP_DEFINE_BIT(Init) \ - WPP_DEFINE_BIT(Shutdown) \ - WPP_DEFINE_BIT(Filter) \ - WPP_DEFINE_BIT(Core) \ - ) +#define WPP_CONTROL_GUIDS \ + WPP_DEFINE_CONTROL_GUID( \ + P4VFS_DRIVER_TRACE_GUID, (082E7434, 2FF0, 4DE7, 8470, 1BBBD2E48237), \ + WPP_DEFINE_BIT(Init) \ + WPP_DEFINE_BIT(Shutdown) \ + WPP_DEFINE_BIT(Filter) \ + WPP_DEFINE_BIT(Core) \ + ) #define WPP_LEVEL_FLAGS_LOGGER(lvl, flags) \ - WPP_LEVEL_LOGGER(flags) + WPP_LEVEL_LOGGER(flags) #define WPP_LEVEL_FLAGS_ENABLED(lvl, flags) \ - (WPP_LEVEL_ENABLED(flags) && WPP_CONTROL(WPP_BIT_ ## flags).Level >= lvl) + (WPP_LEVEL_ENABLED(flags) && WPP_CONTROL(WPP_BIT_ ## flags).Level >= lvl) + +#define WPP_LOGFILEIDPATH(x) \ + WPP_LOGPAIR(sizeof(USHORT), &(x)->Length) \ + WPP_LOGPAIR((x)->Length, (x)->Buffer) + +#define WPP_LOGHEXDUMP(x) \ + WPP_LOGPAIR(sizeof(USHORT), &(x).Length) \ + WPP_LOGPAIR((x).Length, (x).Buffer) + +typedef struct _WPP_HEXDUMP +{ + USHORT Length; + __field_bcount(Length) CONST VOID* Buffer; +} WPP_HEXDUMP, *PWPP_HEXDUMP; + +__inline +WPP_HEXDUMP +P4vfsCreateWppHexDump( + __in ULONG length, + __in_bcount(length) CONST VOID* buffer + ) +{ + WPP_HEXDUMP WppHexDump; + if (length > USHORT_MAX) + { + length = USHORT_MAX; + } + WppHexDump.Length = (USHORT)length; + WppHexDump.Buffer = buffer; + return WppHexDump; +} + +#define LOG_HEXDUMP(length, buffer) \ + P4vfsCreateWppHexDump(length, buffer) + // begin_wpp config @@ -31,4 +66,7 @@ // FUNC P4vfsTraceInfo{ LEVEL=TRACE_LEVEL_INFORMATION }(FLAGS, MSG, ...); // USEPREFIX(P4vfsTraceInfo, "%!STDPREFIX! [%!FILE! @ %!LINE!] INFO:%!SPACE!"); +// DEFINE_CPLX_TYPE(HEXDUMP, WPP_LOGHEXDUMP, WPP_HEXDUMP, ItemHEXDump, "s", _HEX_, 0, 2); +// DEFINE_CPLX_TYPE(FILEIDPATH, WPP_LOGFILEIDPATH, PUNICODE_STRING, ItemHexBytes, "s", _HEX_, 0, 2); + // end_wpp diff --git a/source/P4VFS.Driver/Include/DriverVersion.h b/source/P4VFS.Driver/Include/DriverVersion.h index 91f0f03..32f6a49 100644 --- a/source/P4VFS.Driver/Include/DriverVersion.h +++ b/source/P4VFS.Driver/Include/DriverVersion.h @@ -3,9 +3,9 @@ #pragma once #define P4VFS_VER_MAJOR 1 // Increment this number almost never -#define P4VFS_VER_MINOR 29 // Increment this number whenever the driver changes -#define P4VFS_VER_BUILD 4 // Increment this number when a major user mode change has been made -#define P4VFS_VER_REVISION 0 // Increment this number when we rebuild with any change +#define P4VFS_VER_MINOR 30 // Increment this number whenever the driver changes +#define P4VFS_VER_BUILD 0 // Increment this number when a major user mode change has been made +#define P4VFS_VER_REVISION 16 // Increment this number when we rebuild with any change #define P4VFS_VER_STRINGIZE_EX(v) L#v #define P4VFS_VER_STRINGIZE(v) P4VFS_VER_STRINGIZE_EX(v) diff --git a/source/P4VFS.Driver/P4VFS.Driver.vcxproj b/source/P4VFS.Driver/P4VFS.Driver.vcxproj index f64dafe..afca440 100644 --- a/source/P4VFS.Driver/P4VFS.Driver.vcxproj +++ b/source/P4VFS.Driver/P4VFS.Driver.vcxproj @@ -142,6 +142,15 @@ + + + + + + + + + diff --git a/source/P4VFS.Driver/P4VFS.Driver.vcxproj.filters b/source/P4VFS.Driver/P4VFS.Driver.vcxproj.filters index 1cf80f5..cd49e9d 100644 --- a/source/P4VFS.Driver/P4VFS.Driver.vcxproj.filters +++ b/source/P4VFS.Driver/P4VFS.Driver.vcxproj.filters @@ -12,6 +12,9 @@ {7d8d6eb4-5bd6-4f2d-aa81-fe952550cb96} + + {f2284f34-e352-45ea-be40-841a882442ef} + @@ -46,4 +49,27 @@ + + + Scripts + + + Scripts + + + Scripts + + + Scripts + + + Scripts + + + Scripts + + + Scripts + + \ No newline at end of file diff --git a/source/P4VFS.Driver/Scripts/TestPush.bat b/source/P4VFS.Driver/Scripts/TestPush.bat new file mode 100644 index 0000000..ac86251 --- /dev/null +++ b/source/P4VFS.Driver/Scripts/TestPush.bat @@ -0,0 +1,22 @@ +@ECHO OFF +SETLOCAL ENABLEDELAYEDEXPANSION + +SET SCRIPT_FOLDER=%~dp0 +SET SCRIPT_FOLDER=%SCRIPT_FOLDER:~,-1% +SET REPO_FOLDER=%SCRIPT_FOLDER%\..\..\.. + +SET DEPLOY_COMPUTERNAME=%COMPUTERNAME%-w11 +IF NOT "%1" == "" ( + SET DEPLOY_COMPUTERNAME=%1 +) + +SET DEPLOY_FOLDER=\\%DEPLOY_COMPUTERNAME%\C$\P4VFS +SET ROBOCOPY_COMMON_OPTIONS=/XD .* *.tlog lib include /MT /E /NJH /NJS /NP + +robocopy.exe %REPO_FOLDER%\source %DEPLOY_FOLDER%\source %ROBOCOPY_COMMON_OPTIONS% +robocopy.exe %REPO_FOLDER%\intermediate\builds\P4VFS.Setup %DEPLOY_FOLDER%\intermediate\builds\P4VFS.Setup %ROBOCOPY_COMMON_OPTIONS% +robocopy.exe %REPO_FOLDER%\intermediate\builds\P4VFS.Driver %DEPLOY_FOLDER%\intermediate\builds\P4VFS.Driver %ROBOCOPY_COMMON_OPTIONS% +robocopy.exe %REPO_FOLDER%\intermediate\builds\P4VFS.Console %DEPLOY_FOLDER%\intermediate\builds\P4VFS.Console %ROBOCOPY_COMMON_OPTIONS% +robocopy.exe %REPO_FOLDER%\external\P4API %DEPLOY_FOLDER%\external\P4API %ROBOCOPY_COMMON_OPTIONS% /PURGE + +EXIT /B 0 diff --git a/source/P4VFS.Driver/Scripts/TestSetup.bat b/source/P4VFS.Driver/Scripts/TestSetup.bat index 8a3f376..da27452 100644 --- a/source/P4VFS.Driver/Scripts/TestSetup.bat +++ b/source/P4VFS.Driver/Scripts/TestSetup.bat @@ -27,5 +27,19 @@ IF NOT "%ERRORLEVEL%"=="0" ( EXIT /B 1 ) +:: Enable Windows Developer Mode (allows unprivileged symbolic link creation) +reg.exe add HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock /v AllowDevelopmentWithoutDevLicense /t REG_DWORD /d 1 /f +IF NOT "%ERRORLEVEL%"=="0" ( + ECHO Failed to enable Windows Developer Mode + EXIT /B 1 +) + +:: Ensure the VHD powershell module is installed +powershell.exe -NoProfile -ExecutionPolicy Bypass -Command "$ProgressPreference='SilentlyContinue'; if (-not (Get-Command New-VHD -ErrorAction SilentlyContinue)) { Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Hyper-V-Management-PowerShell -All -NoRestart }" +IF NOT "%ERRORLEVEL%"=="0" ( + ECHO Failed to install New-VHD module + EXIT /B 1 +) + ECHO Successfully setup machine for test drivers. Reboot is probably required. EXIT /B 0 diff --git a/source/P4VFS.Driver/Source/DriverCore.c b/source/P4VFS.Driver/Source/DriverCore.c index b69da4b..c6089fa 100644 --- a/source/P4VFS.Driver/Source/DriverCore.c +++ b/source/P4VFS.Driver/Source/DriverCore.c @@ -230,12 +230,12 @@ P4vfsUserModeResolveFile( ULONG requestMsgSize = sizeof(P4VFS_SERVICE_MSG); - const ULONG volumeNameSize = pFileNameInfo->Volume.Length + sizeof(WCHAR); - const ULONG volumeNameOffset = requestMsgSize; + CONST ULONG volumeNameSize = pFileNameInfo->Volume.Length + sizeof(WCHAR); + CONST ULONG volumeNameOffset = requestMsgSize; requestMsgSize += volumeNameSize; - const ULONG dataNameSize = pFileNameInfo->Name.Length + sizeof(WCHAR); - const ULONG dataNameOffset = requestMsgSize; + CONST ULONG dataNameSize = pFileNameInfo->Name.Length + sizeof(WCHAR); + CONST ULONG dataNameOffset = requestMsgSize; requestMsgSize += dataNameSize; pRequestMsg = (P4VFS_SERVICE_MSG*)ExAllocatePoolZero( @@ -460,7 +460,9 @@ P4vfsIsEqualActionFileKey( if (fileKey0->Length == fileKey1->Length && fileKey0->Buffer != NULL && fileKey1->Buffer != NULL) { if (RtlCompareMemory(fileKey0->Buffer, fileKey1->Buffer, fileKey0->Length) == fileKey0->Length) + { return TRUE; + } } return FALSE; @@ -476,7 +478,9 @@ P4vfsQueryAnyReparseActionInProgress( ExAcquireFastMutex(&g_FltContext.hReparseActionLock); { if (g_FltContext.pReparseActionList != NULL) + { result = TRUE; + } } ExReleaseFastMutex(&g_FltContext.hReparseActionLock); return result; @@ -688,7 +692,7 @@ P4vfsCopyAssignUnicodeString( _In_ P4VFS_UNICODE_STRING* pTargetString, _In_ VOID* pTargetBuffer, _In_ ULONG targetBufferSize, - _In_ const VOID* pSourceBuffer, + _In_ CONST VOID* pSourceBuffer, _In_ ULONG sourceBufferSize ) { @@ -717,8 +721,8 @@ P4vfsCopyAssignUnicodeString( goto CLEANUP; } - const INT64 ptrOffset = ((CHAR*)pTargetBuffer) - ((CHAR*)pTargetString); - const LONG longOffset = (LONG)ptrOffset; + CONST INT64 ptrOffset = ((CHAR*)pTargetBuffer) - ((CHAR*)pTargetString); + CONST LONG longOffset = (LONG)ptrOffset; if (((INT64)longOffset) != ptrOffset) { status = STATUS_INTEGER_OVERFLOW; @@ -770,8 +774,10 @@ P4vfsAllocateUnicodeString( NTSTATUS P4vfsToUnicodeString( - _In_ const P4VFS_UNICODE_STRING* pSrcString, - _Out_ UNICODE_STRING* pDstString + _In_ CONST P4VFS_UNICODE_STRING* pSrcString, + _Out_ UNICODE_STRING* pDstString, + _In_ CONST VOID* pSrcBuffer, + _In_ CONST ULONG dwSrcBufferLength ) { PAGED_CODE(); @@ -781,16 +787,37 @@ P4vfsToUnicodeString( return STATUS_INVALID_PARAMETER; } + // The P4VFS_UNICODE_STRING must have a valid string length if (pSrcString->sizeBytes >= UNICODE_STRING_MAX_BYTES) { return STATUS_BUFFER_OVERFLOW; } + // The P4VFS_UNICODE_STRING must have a WCHAR string length if (pSrcString->sizeBytes % sizeof(WCHAR)) { return STATUS_DATATYPE_MISALIGNMENT; } - + + // The P4VFS_UNICODE_STRING header must lie within the pSrcBuffer up to dwSrcBufferLength + CONST BYTE* pSrcBufferBegin = (CONST BYTE*)pSrcBuffer; + CONST BYTE* pSrcBufferEnd = pSrcBufferBegin + dwSrcBufferLength; + CONST BYTE* pSrcStr = (CONST BYTE*)pSrcString; + if (pSrcStr < pSrcBufferBegin || (pSrcStr + sizeof(P4VFS_UNICODE_STRING)) > pSrcBufferEnd) + { + return STATUS_INVALID_ADDRESS; + } + + // The P4VFS_UNICODE_STRING payload must lie within the buffer. Compute as + // offsets relative to the buffer base so that it's overflow-safe LONGLONG arithmetic + CONST LONGLONG headerOffset = (LONGLONG)(pSrcStr - pSrcBufferBegin); + CONST LONGLONG dataBegin = headerOffset + (LONGLONG)pSrcString->offsetBytes; + CONST LONGLONG dataEnd = dataBegin + (LONGLONG)pSrcString->sizeBytes; + if (dataBegin < 0 || dataEnd < dataBegin || dataEnd > (LONGLONG)dwSrcBufferLength) + { + return STATUS_INVALID_OFFSET_ALIGNMENT; + } + pDstString->Length = (USHORT)(pSrcString->sizeBytes >= sizeof(WCHAR) ? pSrcString->sizeBytes-sizeof(WCHAR) : 0); pDstString->MaximumLength = (USHORT)pSrcString->sizeBytes; pDstString->Buffer = (WCHAR*)P4VFS_UNICODE_STRING_CSTR(*pSrcString); @@ -855,7 +882,7 @@ P4vfsSetFileWritable( if (!NT_SUCCESS(status)) { - P4vfsTraceError(Core, L"P4vfsSetFileWritable: FltCreateFileEx2 failed fileIdPath [%wZ] [%!STATUS!]", pFileIdPath, status); + P4vfsTraceError(Core, L"P4vfsSetFileWritable: FltCreateFileEx2 failed fileIdPath [%!FILEIDPATH!] [%!STATUS!]", pFileIdPath, status); goto CLEANUP; } @@ -868,7 +895,7 @@ P4vfsSetFileWritable( if (!NT_SUCCESS(status)) { - P4vfsTraceError(Core, L"P4vfsSetFileWritable: FltQueryInformationFile FileBasicInformation fileIdPath [%wZ] [%!STATUS!]", pFileIdPath, status); + P4vfsTraceError(Core, L"P4vfsSetFileWritable: FltQueryInformationFile FileBasicInformation fileIdPath [%!FILEIDPATH!] [%!STATUS!]", pFileIdPath, status); goto CLEANUP; } @@ -884,7 +911,7 @@ P4vfsSetFileWritable( if (!NT_SUCCESS(status)) { - P4vfsTraceError(Core, L"P4vfsSetFileWritable: FltSetInformationFile FileBasicInformation fileIdPath [%wZ] [%!STATUS!]", pFileIdPath, status); + P4vfsTraceError(Core, L"P4vfsSetFileWritable: FltSetInformationFile FileBasicInformation fileIdPath [%!FILEIDPATH!] [%!STATUS!]", pFileIdPath, status); goto CLEANUP; } } @@ -1038,7 +1065,7 @@ P4vfsGetFileIdByFileName( if (!NT_SUCCESS(status)) { - P4vfsTraceError(Core, L"P4vfsGetFileIdByFileName: RtlAppendUnicodeToString separator failed [%wZ] fileIdPath [%wZ] [%!STATUS!]", pFileName, &fileIdPath, status); + P4vfsTraceError(Core, L"P4vfsGetFileIdByFileName: RtlAppendUnicodeToString separator failed [%wZ] fileIdPath [%!FILEIDPATH!] [%!STATUS!]", pFileName, &fileIdPath, status); goto CLEANUP; } @@ -1051,7 +1078,7 @@ P4vfsGetFileIdByFileName( if (!NT_SUCCESS(status)) { - P4vfsTraceError(Core, L"P4vfsGetFileIdByFileName: RtlAppendUnicodeToString fileIdString failed [%wZ] fileIdPath [%wZ] [%!STATUS!]", pFileName, &fileIdPath, status); + P4vfsTraceError(Core, L"P4vfsGetFileIdByFileName: RtlAppendUnicodeToString fileIdString failed [%wZ] fileIdPath [%!FILEIDPATH!] [%!STATUS!]", pFileName, &fileIdPath, status); goto CLEANUP; } @@ -1093,12 +1120,148 @@ P4vfsGetFileIdByFileName( return status; } +NTSTATUS +P4vfsPushOpenFileObject( + _In_ PFILE_OBJECT pFileObject, + _In_ HANDLE fileHandle, + _Out_ P4VFS_OPEN_FILE_OBJECT* pOpenFileObject + ) +{ + NTSTATUS status = STATUS_SUCCESS; + P4VFS_OPEN_FILE_OBJECT* pLinkedFileObject = NULL; + + PAGED_CODE(); + + if (pOpenFileObject == NULL) + { + status = STATUS_INVALID_PARAMETER; + P4vfsTraceError(Core, L"P4vfsPushOpenFileObject: pOpenFileObject is NULL"); + goto CLEANUP; + } + + pLinkedFileObject = (P4VFS_OPEN_FILE_OBJECT*)ExAllocatePoolZero( + NonPagedPoolNx, + sizeof(P4VFS_OPEN_FILE_OBJECT), + P4VFS_OPEN_FILE_OBJECT_ALLOC_TAG); + + if (pLinkedFileObject == NULL) + { + status = STATUS_INSUFFICIENT_RESOURCES; + P4vfsTraceError(Core, L"P4vfsPushOpenFileObject: Failed to Allocate P4VFS_OPEN_FILE_OBJECT size [%d]", sizeof(P4VFS_OPEN_FILE_OBJECT)); + goto CLEANUP; + } + + ULONGLONG seed = KeQueryPerformanceCounter(NULL).QuadPart; + ULONGLONG nextUniqueId = (ULONGLONG)(ULONG)InterlockedIncrement(&g_FltContext.nFileIdCount); + ULONGLONG processId = seed * (ULONGLONG)(ULONG_PTR)PsGetCurrentProcessId(); + ULONGLONG threadId = seed * (ULONGLONG)(ULONG_PTR)PsGetCurrentThreadId(); + + #define MIX_U64_TO_U16(a) (((a)>>48)&0xFFFF) ^ (((a)>>32)&0xFFFF) ^ (((a)>>16)&0xFFFF) ^ ((a)&0xFFFF) + processId = MIX_U64_TO_U16(processId); + threadId = MIX_U64_TO_U16(threadId); + #undef MIX_U64_TO_U16 + + // The interal pLinkedFileObject will reside in the linked list of P4VFS_OPEN_FILE_OBJECT + // The fileId is a unique and obfuscated 64bit value which will serve well to track this handle in user mode. + // We can also use the fileHandle to cross-reference with the fileId for additional verification + pLinkedFileObject->fltFileHandle.fileId.data = (nextUniqueId<<32) | (processId<<16) | threadId; + pLinkedFileObject->fltFileHandle.fileHandle = fileHandle; + pLinkedFileObject->pFileObject = pFileObject; + + // Insert the pLinkedFileObject into the linked list + // Assign a copy of the pLinkedFileObject which we are now tracking + ExAcquireFastMutex(&g_FltContext.hOpenFileObjectLock); + { + pLinkedFileObject->pNext = g_FltContext.pOpenFileObjectList; + g_FltContext.pOpenFileObjectList = pLinkedFileObject; + + *pOpenFileObject = *pLinkedFileObject; + pOpenFileObject->pNext = NULL; + } + ExReleaseFastMutex(&g_FltContext.hOpenFileObjectLock); + +CLEANUP: + return status; +} + +NTSTATUS +P4vfsPopOpenFileObject( + _In_ P4VFS_FLT_FILE_HANDLE* pFileHandle, + _Out_ P4VFS_OPEN_FILE_OBJECT* pOpenFileObject + ) +{ + NTSTATUS status = STATUS_SUCCESS; + P4VFS_OPEN_FILE_OBJECT* pObject = NULL; + P4VFS_OPEN_FILE_OBJECT* pPrevObject = NULL; + P4VFS_OPEN_FILE_OBJECT* pFreeObject = NULL; + + PAGED_CODE(); + + if (pOpenFileObject == NULL) + { + status = STATUS_INVALID_PARAMETER; + P4vfsTraceError(Core, L"P4vfsPopOpenFileObject: pOpenFileObject is NULL"); + goto CLEANUP; + } + + if (pFileHandle == NULL) + { + status = STATUS_INVALID_PARAMETER; + P4vfsTraceError(Core, L"P4vfsPopOpenFileObject: pFileHandle is NULL"); + goto CLEANUP; + } + + // Search our linked list of known file objects for one with this pFileId and fileHandle + ExAcquireFastMutex(&g_FltContext.hOpenFileObjectLock); + { + for (pObject = g_FltContext.pOpenFileObjectList; pObject != NULL; pObject = pObject->pNext) + { + if (pObject->fltFileHandle.fileId.data == pFileHandle->fileId.data && pObject->fltFileHandle.fileHandle == pFileHandle->fileHandle) + { + break; + } + + pPrevObject = pObject; + } + + if (pObject != NULL) + { + // Remove the pObject that we found from the linked list + if (g_FltContext.pOpenFileObjectList == pObject) + { + g_FltContext.pOpenFileObjectList = pObject->pNext; + } + else if (pPrevObject != NULL) + { + pPrevObject->pNext = pObject->pNext; + } + + // Return a copy of the object that we've removed and free it + *pOpenFileObject = *pObject; + pOpenFileObject->pNext = NULL; + pFreeObject = pObject; + } + else + { + status = STATUS_NOT_FOUND; + } + } + ExReleaseFastMutex(&g_FltContext.hOpenFileObjectLock); + +CLEANUP: + if (pFreeObject != NULL) + { + ExFreePoolWithTag(pFreeObject, P4VFS_OPEN_FILE_OBJECT_ALLOC_TAG); + } + + return status; +} + NTSTATUS P4vfsOpenReparsePoint( _In_ PUNICODE_STRING pFileName, _In_ ACCESS_MASK desiredAccess, - _Out_ PHANDLE pTargetHandle, - _Outptr_ PFILE_OBJECT* ppTargetFileObject + _Out_ P4VFS_FLT_FILE_HANDLE* pFileHandle ) { NTSTATUS status = STATUS_SUCCESS; @@ -1109,9 +1272,17 @@ P4vfsOpenReparsePoint( PFILE_OBJECT pLocalFileObject = NULL; UNICODE_STRING fileIdPath = {0}; PFLT_INSTANCE pFltInstance = NULL; + P4VFS_OPEN_FILE_OBJECT openFileObject = {0}; PAGED_CODE(); + if (pFileHandle == NULL) + { + status = STATUS_INVALID_PARAMETER; + P4vfsTraceError(Core, L"P4vfsOpenReparsePoint: pFileHandle is NULL"); + goto CLEANUP; + } + // We wish to open an existing reparse point file by using FILE_OPEN_BY_FILE_ID so as to avoid // directory notifications. We take this opportunity to query our PFLT_INSTANCE for the volume // which holds this file, which will be optimal for future filter operations @@ -1141,11 +1312,12 @@ P4vfsOpenReparsePoint( createContext.SiloContext = PsGetHostSilo(); // We use the fileIdPath in place of the pFileName path for FILE_OPEN_BY_FILE_ID. + // Use OBJ_FORCE_ACCESS_CHECK to verify that the application has the necessary access. // The IO_IGNORE_SHARE_ACCESS_CHECK is used to avoid existing share conflicts InitializeObjectAttributes(&objectAttributes, &fileIdPath, - OBJ_CASE_INSENSITIVE, + OBJ_CASE_INSENSITIVE | OBJ_FORCE_ACCESS_CHECK, NULL, NULL); @@ -1168,15 +1340,27 @@ P4vfsOpenReparsePoint( if (!NT_SUCCESS(status)) { - P4vfsTraceError(Core, L"P4vfsReopenFile: FltCreateFileEx2 failed [%wZ] fileIdPath [%wZ] [%!STATUS!]", pFileName, &fileIdPath, status); + P4vfsTraceError(Core, L"P4vfsOpenReparsePoint: FltCreateFileEx2 failed [%wZ] fileIdPath [%!FILEIDPATH!] [%!STATUS!]", pFileName, &fileIdPath, status); + goto CLEANUP; + } + + // The kernel PFILE_OBJECT will remain private. We will only expose the user mode HANDLE along with an opaque P4VFS_FLT_FILE_ID. + // Future operations on this PFILE_OBJECT will restricted behind the P4VFS_FLT_FILE_ID and our internal P4VFS_OPEN_FILE_OBJECT. + + status = P4vfsPushOpenFileObject(pLocalFileObject, + hLocalFile, + &openFileObject); + + if (!NT_SUCCESS(status)) + { + P4vfsTraceError(Core, L"P4vfsOpenReparsePoint: P4vfsPushOpenFileObject failed [%wZ] fileIdPath [%!FILEIDPATH!] [%!STATUS!]", pFileName, &fileIdPath, status); goto CLEANUP; } - P4vfsTraceInfo(Core, L"P4vfsReopenFile: FltCreateFileEx2 success [%wZ] fileIdPath [%wZ] [%!STATUS!]", pFileName, &fileIdPath, status); + *pFileHandle = openFileObject.fltFileHandle; + P4vfsTraceInfo(Core, L"P4vfsOpenReparsePoint: FltCreateFileEx2 success [%wZ] fileIdPath [%!FILEIDPATH!] Id [0x%016I64X] [%!STATUS!]", pFileName, &fileIdPath, openFileObject.fltFileHandle.fileId.data, status); - *pTargetHandle = hLocalFile; hLocalFile = NULL; - *ppTargetFileObject = pLocalFileObject; pLocalFileObject = NULL; CLEANUP: @@ -1205,23 +1389,45 @@ P4vfsOpenReparsePoint( NTSTATUS P4vfsCloseReparsePoint( - _In_ HANDLE hFile, - _In_ PFILE_OBJECT pFileObject + _In_ P4VFS_FLT_FILE_HANDLE* pFileHandle ) { + NTSTATUS status = STATUS_SUCCESS; + P4VFS_OPEN_FILE_OBJECT openFileObject = {0}; + PAGED_CODE(); - if (hFile != NULL) + if (pFileHandle == NULL) + { + status = STATUS_INVALID_PARAMETER; + P4vfsTraceError(Core, L"P4vfsCloseReparsePoint: pFileHandle is NULL"); + goto CLEANUP; + } + + // We do not trust the HANDLE passed in from P4VFS_FLT_FILE_HANDLE. Instead we'll look for the P4VFS_OPEN_FILE_OBJECT by Id + // and confirm the HANDLE and ownership is what we expect. + + status = P4vfsPopOpenFileObject(pFileHandle, + &openFileObject); + + if (!NT_SUCCESS(status)) { - FltClose(hFile); + P4vfsTraceError(Core, L"P4vfsCloseReparsePoint: P4vfsPopOpenFileObject failed with fileId [0x%016I64X] [%!STATUS!]", pFileHandle->fileId.data, status); + goto CLEANUP; } - if (pFileObject != NULL) + if (openFileObject.fltFileHandle.fileHandle != NULL) { - ObDereferenceObject(pFileObject); + FltClose(openFileObject.fltFileHandle.fileHandle); } - return STATUS_SUCCESS; + if (openFileObject.pFileObject != NULL) + { + ObDereferenceObject(openFileObject.pFileObject); + } + +CLEANUP: + return status; } BOOLEAN @@ -1232,7 +1438,7 @@ P4vfsIsCurrentProcessElevated( NTSTATUS status = STATUS_SUCCESS; PEPROCESS pProcessObject = NULL; PACCESS_TOKEN pAccessToken = NULL; - PTOKEN_ELEVATION_TYPE pElevationType = NULL; + PTOKEN_ELEVATION pElevation = NULL; PAGED_CODE(); @@ -1243,6 +1449,9 @@ P4vfsIsCurrentProcessElevated( goto CLEANUP; } + // Reference the process primary token explicitly. This is unaffected by any impersonation + // that may be active on the current thread. + pAccessToken = PsReferencePrimaryToken(pProcessObject); if (pAccessToken == NULL) { @@ -1250,27 +1459,31 @@ P4vfsIsCurrentProcessElevated( goto CLEANUP; } + // Use TokenElevation (TOKEN_ELEVATION::TokenIsElevated) rather than TokenElevationType. + // TokenElevationType may not report TokenElevationTypeFull type while impersonating, whereas + // TokenIsElevated reflects the true elevation state of the primary token. + status = SeQueryInformationToken(pAccessToken, - TokenElevationType, - &pElevationType); + TokenElevation, + &pElevation); - if (!NT_SUCCESS(status) || pElevationType == NULL) + if (!NT_SUCCESS(status) || pElevation == NULL) { - P4vfsTraceError(Core, L"P4vfsIsCurrentProcessElevated: Failed to query TokenElevationType"); + P4vfsTraceError(Core, L"P4vfsIsCurrentProcessElevated: Failed to query TokenElevation"); goto CLEANUP; } - if (*pElevationType == TokenElevationTypeFull) + if (pElevation->TokenIsElevated != 0) { result = TRUE; } - P4vfsTraceInfo(Core, L"P4vfsIsCurrentProcessElevated: Result [%d] pElevationType [%d]", (LONG)result, (LONG)(*pElevationType)); + P4vfsTraceInfo(Core, L"P4vfsIsCurrentProcessElevated: Result [%d] TokenIsElevated [%d]", (LONG)result, (LONG)(pElevation->TokenIsElevated)); CLEANUP: - if (pElevationType != NULL) + if (pElevation != NULL) { - ExFreePool(pElevationType); + ExFreePool(pElevation); } if (pAccessToken != NULL) @@ -1281,3 +1494,50 @@ P4vfsIsCurrentProcessElevated( return result; } +NTSTATUS +P4vfsReadUserMemory( + _Out_ PVOID pTargetBuffer, + _In_ CONST VOID* pUserSourceBuffer, + _In_ ULONG dwLength + ) +{ + NTSTATUS status = STATUS_SUCCESS; + + PAGED_CODE(); + + __try + { + ProbeForRead((PVOID)pUserSourceBuffer, dwLength, sizeof(UCHAR)); + RtlCopyMemory(pTargetBuffer, pUserSourceBuffer, dwLength); + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + status = GetExceptionCode(); + } + + return status; +} + +NTSTATUS +P4vfsWriteUserMemory( + _Out_ PVOID pUserTargetBuffer, + _In_ CONST VOID* pSourceBuffer, + _In_ ULONG dwLength + ) +{ + NTSTATUS status = STATUS_SUCCESS; + + PAGED_CODE(); + + __try + { + ProbeForWrite(pUserTargetBuffer, dwLength, sizeof(UCHAR)); + RtlCopyMemory(pUserTargetBuffer, pSourceBuffer, dwLength); + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + status = GetExceptionCode(); + } + + return status; +} diff --git a/source/P4VFS.Driver/Source/DriverFilter.c b/source/P4VFS.Driver/Source/DriverFilter.c index 8b033b2..4acda82 100644 --- a/source/P4VFS.Driver/Source/DriverFilter.c +++ b/source/P4VFS.Driver/Source/DriverFilter.c @@ -13,6 +13,7 @@ Module Name: --*/ #include +#include #include "DriverCore.h" #include "DriverFilter.h" #include "DriverFilter.tmh" @@ -341,6 +342,7 @@ DriverEntry( goto CLEANUP; } + // Grant all access to the control port as each command will enforce elevated permission as necessary RtlSetDaclSecurityDescriptor(controlSecurityDescriptor, TRUE, NULL, FALSE); if (!NT_SUCCESS(status)) { @@ -374,8 +376,9 @@ DriverEntry( goto CLEANUP; } - // Initialize the reparse action mutex + // Initialize required mutexes ExInitializeFastMutex(&g_FltContext.hReparseActionLock); + ExInitializeFastMutex(&g_FltContext.hOpenFileObjectLock); // After we have created everything we needed, actually start filtering status = FltStartFiltering(g_FltContext.pFilter); @@ -720,21 +723,23 @@ P4vfsControlPortMessage( ) { NTSTATUS status = STATUS_SUCCESS; - P4VFS_CONTROL_MSG* input = NULL; - P4VFS_CONTROL_REPLY* output = NULL; + P4VFS_CONTROL_MSG* pInputMsg = NULL; + P4VFS_CONTROL_REPLY* pOutputReply = NULL; + ULONG dwInputMsgLength = 0; + ULONG dwOutputReplyLength = 0; UNREFERENCED_PARAMETER(pPortCookie); PAGED_CODE(); - if (pInputBuffer == NULL || dwInputBufferLength < sizeof(P4VFS_CONTROL_MSG)) + if (pInputBuffer == NULL || dwInputBufferLength < sizeof(P4VFS_CONTROL_MSG) || dwInputBufferLength > P4VFS_CONTROL_MAX_BUFFER_LENGTH) { status = STATUS_INVALID_PARAMETER; P4vfsTraceError(Filter, L"P4vfsControlPortMessage: pInputBuffer is invalid [%ld]", dwInputBufferLength); goto CLEANUP; } - if (pOutputBuffer == NULL || dwOutputBufferLength < sizeof(P4VFS_CONTROL_REPLY)) + if (pOutputBuffer == NULL || dwOutputBufferLength < sizeof(P4VFS_CONTROL_REPLY) || dwOutputBufferLength > P4VFS_CONTROL_MAX_BUFFER_LENGTH) { status = STATUS_INVALID_PARAMETER; P4vfsTraceError(Filter, L"P4vfsControlPortMessage: pOutputBuffer is invalid [%ld]", dwOutputBufferLength); @@ -747,28 +752,60 @@ P4vfsControlPortMessage( P4vfsTraceError(Filter, L"P4vfsControlPortMessage: pReturnOutputBufferLength is NULL"); goto CLEANUP; } - - input = (P4VFS_CONTROL_MSG*)pInputBuffer; - output = (P4VFS_CONTROL_REPLY*)pOutputBuffer; - RtlZeroMemory(output, sizeof(P4VFS_CONTROL_REPLY)); - output->operation = input->operation; - *pReturnOutputBufferLength = sizeof(P4VFS_CONTROL_REPLY); + // Copy the user-mode pInputBuffer safely into kernel-mode memory to ensure proper access + // and exclusive use in this routine + + dwInputMsgLength = dwInputBufferLength; + pInputMsg = (P4VFS_CONTROL_MSG*)ExAllocatePoolZero( + NonPagedPoolNx, + dwInputMsgLength, + P4VFS_CONTROL_MSG_ALLOC_TAG); + + if (pInputMsg == NULL) + { + status = STATUS_INSUFFICIENT_RESOURCES; + P4vfsTraceError(Filter, L"P4vfsControlPortMessage: Failed to allocate P4VFS_CONTROL_MSG size [%d]", dwInputMsgLength); + goto CLEANUP; + } + + status = P4vfsReadUserMemory(pInputMsg, pInputBuffer, dwInputMsgLength); + if (!NT_SUCCESS(status)) + { + P4vfsTraceError(Filter, L"P4vfsControlPortMessage: Failed to read user-mode pInputBuffer size [%d]", dwInputMsgLength); + goto CLEANUP; + } + + // Create a reply buffer in kernel-mode memory for exclusive use in this routine, and then safely + // write to the user-mode pOutputBuffer at the end + + dwOutputReplyLength = sizeof(P4VFS_CONTROL_REPLY); + pOutputReply = (P4VFS_CONTROL_REPLY*)ExAllocatePoolZero( + NonPagedPoolNx, + dwOutputReplyLength, + P4VFS_CONTROL_REPLY_ALLOC_TAG); - switch (input->operation) + if (pOutputReply == NULL) + { + status = STATUS_INSUFFICIENT_RESOURCES; + P4vfsTraceError(Filter, L"P4vfsControlPortMessage: Failed to allocate P4VFS_CONTROL_REPLY size [%d]", dwOutputReplyLength); + goto CLEANUP; + } + + switch (pInputMsg->operation) { case P4VFS_OPERATION_GET_IS_CONNECTED: { - output->data.GET_IS_CONNECTED.connected = g_FltContext.pServiceClientPort ? 1 : 0; - P4vfsTraceInfo(Filter, L"P4vfsControlPortMessage: P4VFS_CONTROL_GET_IS_CONNECTED [%d]", output->data.GET_IS_CONNECTED.connected); + pOutputReply->data.GET_IS_CONNECTED.connected = g_FltContext.pServiceClientPort ? 1 : 0; + P4vfsTraceInfo(Filter, L"P4vfsControlPortMessage: P4VFS_CONTROL_GET_IS_CONNECTED [%d]", pOutputReply->data.GET_IS_CONNECTED.connected); break; } case P4VFS_OPERATION_GET_VERSION: { - output->data.GET_VERSION.major = P4VFS_VER_MAJOR; - output->data.GET_VERSION.minor = P4VFS_VER_MINOR; - output->data.GET_VERSION.build = P4VFS_VER_BUILD; - output->data.GET_VERSION.revision = P4VFS_VER_REVISION; + pOutputReply->data.GET_VERSION.major = P4VFS_VER_MAJOR; + pOutputReply->data.GET_VERSION.minor = P4VFS_VER_MINOR; + pOutputReply->data.GET_VERSION.build = P4VFS_VER_BUILD; + pOutputReply->data.GET_VERSION.revision = P4VFS_VER_REVISION; P4vfsTraceInfo(Filter, L"P4vfsControlPortMessage: P4VFS_CONTROL_GET_VERSION [%ls]", P4VFS_VER_VERSION_STRING); break; } @@ -781,18 +818,18 @@ P4vfsControlPortMessage( break; } - const WCHAR strSanitizeAttributes[] = L"SanitizeAttributes"; - if (RtlCompareMemory(strSanitizeAttributes, input->data.SET_FLAG.name, sizeof(strSanitizeAttributes)) == sizeof(strSanitizeAttributes)) + CONST WCHAR strSanitizeAttributes[] = L"SanitizeAttributes"; + if (RtlCompareMemory(strSanitizeAttributes, pInputMsg->data.SET_FLAG.name, sizeof(strSanitizeAttributes)) == sizeof(strSanitizeAttributes)) { - g_FltContext.bSanitizeAttributes = !!input->data.SET_FLAG.value; + g_FltContext.bSanitizeAttributes = !!pInputMsg->data.SET_FLAG.value; P4vfsTraceInfo(Filter, L"P4vfsControlPortMessage: P4VFS_CONTROL_SET_FLAG SanitizeAttributes = [0x%08d]", (LONG)g_FltContext.bSanitizeAttributes); break; } - const WCHAR strShareModeDuringHydration[] = L"ShareModeDuringHydration"; - if (RtlCompareMemory(strShareModeDuringHydration, input->data.SET_FLAG.name, sizeof(strShareModeDuringHydration)) == sizeof(strShareModeDuringHydration)) + CONST WCHAR strShareModeDuringHydration[] = L"ShareModeDuringHydration"; + if (RtlCompareMemory(strShareModeDuringHydration, pInputMsg->data.SET_FLAG.name, sizeof(strShareModeDuringHydration)) == sizeof(strShareModeDuringHydration)) { - g_FltContext.bShareModeDuringHydration = !!input->data.SET_FLAG.value; + g_FltContext.bShareModeDuringHydration = !!pInputMsg->data.SET_FLAG.value; P4vfsTraceInfo(Filter, L"P4vfsControlPortMessage: P4VFS_CONTROL_SET_FLAG ShareModeDuringHydration = [0x%08d]", (LONG)g_FltContext.bShareModeDuringHydration); break; } @@ -802,8 +839,19 @@ P4vfsControlPortMessage( } case P4VFS_OPERATION_OPEN_REPARSE_POINT: { + if (P4vfsIsCurrentProcessElevated() == FALSE) + { + P4vfsTraceError(Filter, L"P4vfsControlPortMessage: P4VFS_OPERATION_OPEN_REPARSE_POINT elevation required"); + status = STATUS_ELEVATION_REQUIRED; + break; + } + UNICODE_STRING unicodeFilePath = {0}; - status = P4vfsToUnicodeString(&input->data.OPEN_REPARSE_POINT.filePath, &unicodeFilePath); + status = P4vfsToUnicodeString(&pInputMsg->data.OPEN_REPARSE_POINT.filePath, + &unicodeFilePath, + pInputMsg, + dwInputMsgLength); + if (!NT_SUCCESS(status)) { P4vfsTraceInfo(Filter, L"P4vfsControlPortMessage: P4VFS_OPERATION_OPEN_REPARSE_POINT P4vfsToUnicodeString failed [%!STATUS!]", status); @@ -811,35 +859,63 @@ P4vfsControlPortMessage( } ACCESS_MASK desiredAccess = 0; - desiredAccess |= input->data.OPEN_REPARSE_POINT.accessRead ? FILE_GENERIC_READ : 0; - desiredAccess |= input->data.OPEN_REPARSE_POINT.accessWrite ? FILE_GENERIC_WRITE : 0; - desiredAccess |= input->data.OPEN_REPARSE_POINT.accessDelete ? DELETE : 0; + desiredAccess |= pInputMsg->data.OPEN_REPARSE_POINT.accessRead ? FILE_GENERIC_READ : 0; + desiredAccess |= pInputMsg->data.OPEN_REPARSE_POINT.accessWrite ? FILE_GENERIC_WRITE : 0; + desiredAccess |= pInputMsg->data.OPEN_REPARSE_POINT.accessDelete ? DELETE : 0; status = P4vfsOpenReparsePoint(&unicodeFilePath, desiredAccess, - &output->data.OPEN_REPARSE_POINT.handle.fileHandle, - &output->data.OPEN_REPARSE_POINT.handle.fileObject); + &pOutputReply->data.OPEN_REPARSE_POINT.handle); - output->data.OPEN_REPARSE_POINT.ntstatus = status; + pOutputReply->data.OPEN_REPARSE_POINT.ntstatus = status; break; } case P4VFS_OPERATION_CLOSE_REPARSE_POINT: { - status = P4vfsCloseReparsePoint(input->data.CLOSE_REPARSE_POINT.handle.fileHandle, - input->data.CLOSE_REPARSE_POINT.handle.fileObject); + if (P4vfsIsCurrentProcessElevated() == FALSE) + { + P4vfsTraceError(Filter, L"P4vfsControlPortMessage: P4VFS_OPERATION_CLOSE_REPARSE_POINT elevation required"); + status = STATUS_ELEVATION_REQUIRED; + break; + } - output->data.CLOSE_REPARSE_POINT.ntstatus = status; + status = P4vfsCloseReparsePoint(&pInputMsg->data.CLOSE_REPARSE_POINT.handle); + + pOutputReply->data.CLOSE_REPARSE_POINT.ntstatus = status; break; } default: { - P4vfsTraceInfo(Filter, L"P4vfsControlPortMessage: UNKNOWN 0x%08x", input->operation); + P4vfsTraceInfo(Filter, L"P4vfsControlPortMessage: UNKNOWN 0x%08x", pInputMsg->operation); status = STATUS_INVALID_PARAMETER; break; } } + pOutputReply->operation = pInputMsg->operation; + NTSTATUS writeStatus = P4vfsWriteUserMemory(pOutputBuffer, pOutputReply, dwOutputReplyLength); + if (!NT_SUCCESS(writeStatus)) + { + P4vfsTraceError(Filter, L"P4vfsControlPortMessage: Failed to write user-mode pOutputBuffer overriding operation status [%!STATUS!] with status [%!STATUS!]", status, writeStatus); + status = writeStatus; + goto CLEANUP; + } + + *pReturnOutputBufferLength = dwOutputReplyLength; + CLEANUP: + if (pInputMsg) + { + ExFreePoolWithTag(pInputMsg, P4VFS_CONTROL_MSG_ALLOC_TAG); + pInputMsg = NULL; + } + + if (pOutputReply) + { + ExFreePoolWithTag(pOutputReply, P4VFS_CONTROL_REPLY_ALLOC_TAG); + pOutputReply = NULL; + } + return status; } diff --git a/source/P4VFS.Driver/p4vfsflt.inf b/source/P4VFS.Driver/p4vfsflt.inf index c994fc8..a58703e 100644 --- a/source/P4VFS.Driver/p4vfsflt.inf +++ b/source/P4VFS.Driver/p4vfsflt.inf @@ -57,11 +57,11 @@ AddReg = P4VFSFLT.AddRegistry ;; [P4VFSFLT.AddRegistry] -HKR,,"DebugFlags",0x00010001 ,0x0 -HKR,,"SupportedFeatures",0x00010001,0x3 -HKR,"Instances","DefaultInstance",0x00000000,%DefaultInstance% -HKR,"Instances\"%Instance1.Name%,"Altitude",0x00000000,%Instance1.Altitude% -HKR,"Instances\"%Instance1.Name%,"Flags",0x00010001,%Instance1.Flags% +HKR,"Parameters","DebugFlags",0x00010001,0x0 +HKR,"Parameters","SupportedFeatures",0x00010001,0x3 +HKR,"Parameters\Instances","DefaultInstance",0x00000000,%DefaultInstance% +HKR,"Parameters\Instances\"%Instance1.Name%,"Altitude",0x00000000,%Instance1.Altitude% +HKR,"Parameters\Instances\"%Instance1.Name%,"Flags",0x00010001,%Instance1.Flags% ;; ;; Copy Files Section diff --git a/source/P4VFS.UnitTest/Source/UnitTestBase.cs b/source/P4VFS.UnitTest/Source/UnitTestBase.cs index 8f7cb35..7a200aa 100644 --- a/source/P4VFS.UnitTest/Source/UnitTestBase.cs +++ b/source/P4VFS.UnitTest/Source/UnitTestBase.cs @@ -254,11 +254,7 @@ public void WorkspaceReset(DepotConfig config = null) Assert(File.Exists(settingsPath) == false, String.Format("User settings file not allowed for unit tests: {0}", settingsPath)); } - foreach (string name in new[]{DepotConstants.P4PORT, DepotConstants.P4USER, DepotConstants.P4CLIENT, DepotConstants.P4CONFIG, DepotConstants.P4TRUST, DepotConstants.P4TICKETS}) - { - Environment.SetEnvironmentVariable(name, ""); - Assert(ProcessInfo.ExecuteWait(P4Exe, String.Format("set {0}=", name)) == 0); - } + WorkspaceEnvironmentReset(); AssertRetry(() => VirtualFileSystem.IsDriverLoaded(), message:"IsDriverLoaded"); AssertRetry(() => VirtualFileSystem.IsDriverReady(), message:"IsDriverReady"); @@ -334,6 +330,15 @@ public void WorkspaceReset(DepotConfig config = null) Assert(service.GetServiceSetting(nameof(SettingManager.ExcludedProcessNames)).ToString() == SettingManager.Default.ExcludedProcessNames); } + public static void WorkspaceEnvironmentReset() + { + foreach (string name in new[]{DepotConstants.P4PORT, DepotConstants.P4USER, DepotConstants.P4CLIENT, DepotConstants.P4CONFIG, DepotConstants.P4TRUST, DepotConstants.P4TICKETS}) + { + Environment.SetEnvironmentVariable(name, ""); + Assert(ProcessInfo.ExecuteWait(P4Exe, String.Format("set {0}=", name)) == 0); + } + } + public void ServiceRestart() { VirtualFileSystemLog.Info("Stopping service {0} ...", VirtualFileSystem.ServiceTitle); diff --git a/source/P4VFS.UnitTest/Source/UnitTestCommon.cs b/source/P4VFS.UnitTest/Source/UnitTestCommon.cs index 381acbf..5a8c7a0 100644 --- a/source/P4VFS.UnitTest/Source/UnitTestCommon.cs +++ b/source/P4VFS.UnitTest/Source/UnitTestCommon.cs @@ -1042,7 +1042,7 @@ public void DepotServerConfigTest() // Simple test of expected values of SettingManager.Default Assert(SettingManager.ExcludedProcessNames == SettingManager.Default.ExcludedProcessNames); - Assert(SettingManager.ExcludedProcessNames == "MsSense.exe;MsMpEng.exe;SenseCE.exe;SenseIR.exe;SearchProtocolHost.exe;MpDlpService.exe"); + Assert(SettingManager.ExcludedProcessNames == "MsSense.exe;MsMpEng.exe;SenseCE.exe;SenseIR.exe;SearchProtocolHost.exe;MpDlpService.exe;SenseNdr.exe;SenseDlpProcessor.exe"); Assert(SettingManager.PopulateMethod == SettingManager.Default.PopulateMethod); Assert(SettingManager.PopulateMethod == "Stream"); @@ -1611,6 +1611,7 @@ public void ConnectInvalidConfigTest() WorkspaceReset(); using (DepotClient depotClient = new DepotClient()) { + depotClient.Unattended = true; DepotConfig config = new DepotConfig{ Port=_P4Port, Client="__invalid_p4vfs_client__", User=_P4User }; Assert(depotClient.Connect(config)); DepotResultInfo.Node info = depotClient.Info(); @@ -1623,6 +1624,7 @@ public void ConnectInvalidConfigTest() } using (DepotClient depotClient = new DepotClient()) { + depotClient.Unattended = true; DepotConfig config = new DepotConfig{ Port=_P4Port, Client="__invalid_p4vfs_client__", User="__invalid_user__" }; Assert(depotClient.Connect(config)); DepotResultInfo.Node info = depotClient.Info(); @@ -1635,6 +1637,7 @@ public void ConnectInvalidConfigTest() } using (DepotClient depotClient = new DepotClient()) { + depotClient.Unattended = true; DepotConfig config = new DepotConfig{ Port=_P4Port, Client="__invalid_p4vfs_client__" }; Assert(depotClient.Connect(config)); DepotResultInfo.Node info = depotClient.Info(); @@ -1647,11 +1650,13 @@ public void ConnectInvalidConfigTest() } using (DepotClient depotClient = new DepotClient()) { + depotClient.Unattended = true; DepotConfig config = new DepotConfig{ Port=_P4Port, User="__invalid_user__" }; Assert(depotClient.Connect(config) == false); } using (DepotClient depotClient = new DepotClient()) { + depotClient.Unattended = true; DepotConfig config = new DepotConfig{ Port=_P4Port, Client=_P4Client }; Assert(depotClient.Connect(config)); DepotResultInfo.Node info = depotClient.Info(); @@ -1664,6 +1669,7 @@ public void ConnectInvalidConfigTest() } using (DepotClient depotClient = new DepotClient()) { + depotClient.Unattended = true; DepotConfig config = new DepotConfig{ Port=_P4Port, User=_P4User }; Assert(depotClient.Connect(config)); DepotResultInfo.Node info = depotClient.Info(); @@ -2137,7 +2143,6 @@ public void ShellLoginTimeoutTest() HttpListener loginListener = new HttpListener(); loginListener.Prefixes.Add(loginEndpoint); loginListener.Start(); - Thread loginListenerThread = new Thread(new ThreadStart(() => { diff --git a/source/P4VFS.UnitTest/Source/UnitTestServer.cs b/source/P4VFS.UnitTest/Source/UnitTestServer.cs index ad28ede..900c48a 100644 --- a/source/P4VFS.UnitTest/Source/UnitTestServer.cs +++ b/source/P4VFS.UnitTest/Source/UnitTestServer.cs @@ -37,8 +37,9 @@ public void StartupLocalPerforceServerTest() string serverDatabaseFolder = String.Format("{0}\\db", serverRootFolder); FileUtilities.CreateDirectory(serverDatabaseFolder); string serverLogFile = String.Format("{0}\\p4d.log", serverRootFolder); + string serverJnlFile = String.Format("{0}\\journal", serverDatabaseFolder); string serverDescription = GetServerDescription(_P4Port); - string serverArgs = String.Format("-L \"{0}\" -r \"{1}\" -p {2} -Id {3} -J off", serverLogFile, serverDatabaseFolder, serverPortNumber, serverDescription); + string serverArgs = String.Format("-L \"{0}\" -r \"{1}\" -p {2} -Id {3} -J {4}", serverLogFile, serverDatabaseFolder, serverPortNumber, serverDescription, serverJnlFile); ProcessStartInfo serverStartInfo = new ProcessStartInfo{ FileName = serverP4dExe, @@ -53,6 +54,10 @@ public void StartupLocalPerforceServerTest() Assert(!serverProcess.HasExited, "Server process quit prematuraly"); AssertRetry(() => ProcessInfo.ExecuteWait(P4Exe, String.Format("-p {0} info", _P4Port)) == 0); + // Set the password for the default generated admin for this new database. This user will be deleted afterwards + Assert(ProcessInfo.ExecuteWait(P4Exe, String.Format("-p {0} passwd", _P4Port), echo:true, stdin:String.Format("{0}\n{0}\n", DefaultP4Passwd)) == 0); + Assert(ProcessInfo.ExecuteWait(P4Exe, String.Format("-p {0} login", _P4Port), echo:true, stdin:String.Format("{0}\n", DefaultP4Passwd)) == 0); + string[] serverConfigVariables = new[] { "auth.sso.allow.passwd=1", "db.peeking=3", @@ -62,6 +67,7 @@ public void StartupLocalPerforceServerTest() "monitor=10", "net.parallel.submit.threads=8", "net.parallel.max=8", + "security=4", "server=2", "submit.unlocklocked=1", }; @@ -90,10 +96,8 @@ public void StartupLocalPerforceServerTest() Assert(defaultUsers.Length == 1); string defaultUser = defaultUsers[0]; - // Set a password for the admin, then restart the server for the SSO login options to take effect, then change the admin password as required - Assert(ProcessInfo.ExecuteWait(P4Exe, String.Format("-p {0} -u {1} passwd -P {2}{2}", _P4Port, defaultUser, DefaultP4Passwd), echo:true) == 0); + // Restart the server for the SSO login options to take effect, then change the admin password as required Assert(ProcessInfo.ExecuteWait(P4Exe, String.Format("-p {0} admin restart", _P4Port), echo:true) == 0); - Assert(ProcessInfo.ExecuteWait(P4Exe, String.Format("-p {0} -u {1} passwd", _P4Port, defaultUser), echo:true, stdin:String.Format("{0}{0}\n{0}\n{0}\n", DefaultP4Passwd)) == 0); Assert(ProcessInfo.ExecuteWait(P4Exe, String.Format("-p {0} -u {1} set P4PASSWD=", _P4Port, defaultUser), echo:true) == 0); // Perform initial administrator operations using the default generated admin user (typically current session user name). @@ -621,6 +625,7 @@ public static void CreateDuplicatePerforceServer(string targetPort) string targetRootFolder = GetServerRootFolder(targetPort); string targetDatabaseFolder = String.Format("{0}\\db", targetRootFolder); string targetServerLogFile = String.Format("{0}\\p4d.log", targetRootFolder); + string targetServerJnlFile = String.Format("{0}\\journal", targetDatabaseFolder); string targetServerDescription = GetServerDescription(targetPort); Action copyFileToTarget = (string sourceFilePath) => @@ -647,7 +652,7 @@ public static void CreateDuplicatePerforceServer(string targetPort) string targetP4dExe = String.Format("{0}\\{1}", targetRootFolder, Path.GetFileName(GetServerP4dExe())); Assert(File.Exists(targetP4dExe)); - string targetServerArgs = String.Format("-L \"{0}\" -r \"{1}\" -p {2} -Id {3} -J off", targetServerLogFile, targetDatabaseFolder, targetServerPort, targetServerDescription); + string targetServerArgs = String.Format("-L \"{0}\" -r \"{1}\" -p {2} -Id {3} -J {4}", targetServerLogFile, targetDatabaseFolder, targetServerPort, targetServerDescription, targetServerJnlFile); Assert(ProcessInfo.ExecuteWait(targetP4dExe, String.Format("{0} -jr {1}", targetServerArgs, checkpointFile), echo: true) == 0); Dictionary targetEnvironment = new Dictionary(); @@ -828,6 +833,8 @@ private static void ServerWorkspaceReset(string p4Port = null) AssertRetry(() => { try { FileUtilities.DeleteDirectoryAndFiles(serverRootFolder); return true; } catch {} return false; }); AssertRetry(() => Directory.Exists(serverRootFolder) == false, String.Format("directory exists {0}", serverRootFolder)); + + WorkspaceEnvironmentReset(); } public static string GetServerPortIPAddress(string p4Port = null) diff --git a/source/P4VFS.UnitTest/Source/UnitTestWorkflow.cs b/source/P4VFS.UnitTest/Source/UnitTestWorkflow.cs index ede692e..5fe42e5 100644 --- a/source/P4VFS.UnitTest/Source/UnitTestWorkflow.cs +++ b/source/P4VFS.UnitTest/Source/UnitTestWorkflow.cs @@ -472,6 +472,7 @@ public void DevDriveSupportTest() "; ProcessInfo.ExecuteResultOutput mountOutput = executePowershellCommand(mountDevDriveScript); + VirtualFileSystemLog.Info(mountOutput.Text); Assert(mountOutput?.ExitCode == 0); Assert(File.Exists(devDriveVhd)); diff --git a/source/P4VFS.props b/source/P4VFS.props index 791de78..9a0d430 100644 --- a/source/P4VFS.props +++ b/source/P4VFS.props @@ -65,15 +65,15 @@ $(P4VFSBuildDir)/$(P4VFSUnitTestName) - 2025.2 + 2026.1 $(P4VFSConfiguration) $(P4VFSExternalDir)/P4API/$(PerforceApiVersion) $(PerforceApiDir)/include $(PerforceApiDir)/lib/x64.vs$(P4VFSVisualStudioEdition).dyn.$(PerforceApiConfiguration) - libclient.lib;libp4api.lib;libp4script.lib;libp4script_c.lib;libp4script_curl.lib;libp4script_sqlite.lib;librpc.lib;libsupp.lib + libclient.lib;libp4api.lib;libp4script.lib;libp4script_c.lib;libp4script_cstub.lib;libp4script_curl.lib;libp4script_sqlite.lib;librpc.lib;libsupp.lib - 3.0.18 + 3.0.21 $(P4VFSConfiguration) $(P4VFSExternalDir)/OpenSSL/$(OpenSSLApiVersion) $(OpenSSLApiDir)/include