Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Attempt to mitigate locked machine.config #1825

Merged
merged 1 commit into from
Dec 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 33 additions & 3 deletions GVFS/GVFS.Common/Http/HttpRequestor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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)
Expand Down Expand Up @@ -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;
}
}
}
14 changes: 14 additions & 0 deletions GVFS/GitHooksLoader/GitHooksLoader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<LPWSTR>(commandLine.c_str()),
Expand All @@ -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);
Expand Down