From f9b304c31e2b503878d2f7cbd8e46e0dd9c00511 Mon Sep 17 00:00:00 2001 From: Tyrie Vella Date: Thu, 14 Nov 2024 15:44:58 -0800 Subject: [PATCH 1/3] Attempt to mitigate locked machine.config --- GVFS/GVFS.Common/Http/HttpRequestor.cs | 36 +++++++++++++++++++++++--- GVFS/GitHooksLoader/GitHooksLoader.cpp | 14 ++++++++++ 2 files changed, 47 insertions(+), 3 deletions(-) diff --git a/GVFS/GVFS.Common/Http/HttpRequestor.cs b/GVFS/GVFS.Common/Http/HttpRequestor.cs index ca850c9fb5..e3c163d42b 100644 --- a/GVFS/GVFS.Common/Http/HttpRequestor.cs +++ b/GVFS/GVFS.Common/Http/HttpRequestor.cs @@ -8,6 +8,7 @@ using System.Net; using System.Net.Http; using System.Net.Http.Headers; +using System.Runtime.InteropServices; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -27,9 +28,15 @@ public abstract class HttpRequestor : IDisposable static HttpRequestor() { - ServicePointManager.SecurityProtocol = ServicePointManager.SecurityProtocol | SecurityProtocolType.Tls12; - ServicePointManager.DefaultConnectionLimit = Environment.ProcessorCount; - availableConnections = new SemaphoreSlim(ServicePointManager.DefaultConnectionLimit); + /* If machine.config is locked, then initializing ServicePointManager will fail and be unrecoverable. + * Machine.config locking is typically very brief (~1ms by the antivirus scanner) so we can attempt to lock + * it ourselves (by opening it for read) *beforehand and briefly wait if it's locked */ + using (var machineConfigLock = GetMachineConfigLock()) + { + ServicePointManager.SecurityProtocol = ServicePointManager.SecurityProtocol | SecurityProtocolType.Tls12; + ServicePointManager.DefaultConnectionLimit = Environment.ProcessorCount; + availableConnections = new SemaphoreSlim(ServicePointManager.DefaultConnectionLimit); + } } protected HttpRequestor(ITracer tracer, RetryConfig retryConfig, Enlistment enlistment) @@ -329,5 +336,28 @@ private static bool TryGetResponseMessageFromHttpRequestException(HttpRequestExc return true; } + + private static FileStream GetMachineConfigLock() + { + var machineConfigLocation = RuntimeEnvironment.SystemConfigurationFile; + var tries = 0; + var maxTries = 3; + while (tries++ < maxTries) + { + try + { + /* Opening with FileShare.Read will fail if another process (eg antivirus) has opened the file for write, + but will still let ServicePointManager read the file.*/ + FileStream stream = File.Open(machineConfigLocation, FileMode.Open, FileAccess.Read, FileShare.Read); + return stream; + } + catch (IOException e) when ((uint)e.HResult == 0x80070020) // SHARING_VIOLATION + { + Thread.Sleep(10); + } + } + /* Couldn't get the lock - the process will likely fail. */ + return null; + } } } diff --git a/GVFS/GitHooksLoader/GitHooksLoader.cpp b/GVFS/GitHooksLoader/GitHooksLoader.cpp index d86638ef86..d381932804 100644 --- a/GVFS/GitHooksLoader/GitHooksLoader.cpp +++ b/GVFS/GitHooksLoader/GitHooksLoader.cpp @@ -117,6 +117,18 @@ int ExecuteHook(const std::wstring &applicationName, wchar_t *hookName, int argc si.dwFlags = STARTF_USESTDHANDLES; ZeroMemory(&pi, sizeof(pi)); + + /* The child process will inherit ErrorMode from this process. + * SEM_FAILCRITICALERRORS will prevent the .NET runtime from + * creating a dialog box for critical errors - in particular + * if antivirus has locked the machine.config file. + * Disabling the dialog box lets the child process (typically GVFS.Hooks.exe) + * continue trying to run, and if it still needs machine.config then it + * can handle the exception at that time (whereas the dialog box would + * hang the app until clicked, and is not handleable by our code). + */ + UINT previousErrorMode = SetErrorMode(SEM_FAILCRITICALERRORS); + if (!CreateProcess( NULL, // Application name const_cast(commandLine.c_str()), @@ -131,8 +143,10 @@ int ExecuteHook(const std::wstring &applicationName, wchar_t *hookName, int argc ) { fwprintf(stderr, L"Could not execute '%s'. CreateProcess error (%d).\n", applicationName.c_str(), GetLastError()); + SetErrorMode(previousErrorMode); exit(3); } + SetErrorMode(previousErrorMode); // Wait until child process exits. WaitForSingleObject(pi.hProcess, INFINITE); From efd4740908666aa0811960b26880fd0f346d2d7b Mon Sep 17 00:00:00 2001 From: Matthew John Cheetham Date: Mon, 18 Nov 2024 16:11:12 +0000 Subject: [PATCH 2/3] scripts/Build.bat: specify nuget pkg install dir The Build.bat script installs the vswhere package in order to locate MSBuild if it is not already found on the %PATH%, however it does not correctly make use of the %VFS_PACKAGESDIR% directory that should house all the packages used in the build process (such as the GVFS.ProjFS package). Correct the script by adding the -OutputDirectory argument to the nuget install command for vswhere. --- scripts/Build.bat | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/Build.bat b/scripts/Build.bat index 637b1e18e9..cf8ebefd12 100644 --- a/scripts/Build.bat +++ b/scripts/Build.bat @@ -43,7 +43,7 @@ IF NOT EXIST "%NUGET_EXEC%" ( REM Acquire vswhere to find VS installations reliably SET VSWHERE_VER=2.6.7 -"%NUGET_EXEC%" install vswhere -Version %VSWHERE_VER% || exit /b 1 +"%NUGET_EXEC%" install vswhere -Version %VSWHERE_VER% -OutputDirectory %VFS_PACKAGESDIR% || exit /b 1 SET VSWHERE_EXEC="%VFS_PACKAGESDIR%\vswhere.%VSWHERE_VER%\tools\vswhere.exe" REM Assumes default installation location for Windows 10 SDKs From da1637a219cfb4602e4130da7d42b0da800946c0 Mon Sep 17 00:00:00 2001 From: Tyrie Vella Date: Mon, 2 Dec 2024 16:23:22 -0800 Subject: [PATCH 3/3] Replace x64 check installer with x64compatible PR #1815 updated the installer dependency version, which seems to have caused the installer to enforce the AllowedArchitectures=x64 check (which has been there for years). We've received complaints from users that the latest release refuses to install on their ARM machines, but previous versions did and worked fine with the default x86/64 emulation. The latest version of Innosetup adds x64compatible option for the AllowedArchitectures and ArchitecturesInstallIn64BitMode to address the case of x64 emulation on ARM. --- GVFS/GVFS.Installers/GVFS.Installers.csproj | 2 +- GVFS/GVFS.Installers/Setup.iss | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/GVFS/GVFS.Installers/GVFS.Installers.csproj b/GVFS/GVFS.Installers/GVFS.Installers.csproj index 44c166d565..bf0c5ec7f0 100644 --- a/GVFS/GVFS.Installers/GVFS.Installers.csproj +++ b/GVFS/GVFS.Installers/GVFS.Installers.csproj @@ -12,7 +12,7 @@ - + diff --git a/GVFS/GVFS.Installers/Setup.iss b/GVFS/GVFS.Installers/Setup.iss index 109f4f45bc..886da10428 100644 --- a/GVFS/GVFS.Installers/Setup.iss +++ b/GVFS/GVFS.Installers/Setup.iss @@ -39,8 +39,8 @@ MinVersion=10.0.14374 DisableDirPage=yes DisableReadyPage=yes SetupIconFile="{#LayoutDir}\GitVirtualFileSystem.ico" -ArchitecturesInstallIn64BitMode=x64 -ArchitecturesAllowed=x64 +ArchitecturesInstallIn64BitMode=x64compatible +ArchitecturesAllowed=x64compatible WizardImageStretch=no WindowResizable=no CloseApplications=yes