diff --git a/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/dllmain.cpp b/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/dllmain.cpp index 3fce85afdd..a9c328c3fd 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/dllmain.cpp +++ b/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/dllmain.cpp @@ -10,6 +10,7 @@ #include "resources.h" #include "exceptions.h" #include "EventLog.h" +#include "RegistryKey.h" DECLARE_DEBUG_PRINT_OBJECT("aspnetcorev2.dll"); @@ -88,9 +89,6 @@ HRESULT --*/ { - HKEY hKey {}; - BOOL fDisableANCM = FALSE; - UNREFERENCED_PARAMETER(dwServerVersion); if (pHttpServer->IsCommandLineLaunch()) @@ -102,38 +100,11 @@ HRESULT g_hEventLog = RegisterEventSource(nullptr, ASPNETCORE_EVENT_PROVIDER); } - // check whether the feature is disabled due to security reason - if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, - L"SOFTWARE\\Microsoft\\IIS Extensions\\IIS AspNetCore Module V2\\Parameters", - 0, - KEY_READ, - &hKey) == NO_ERROR) + auto fDisableModule = RegistryKey::TryGetDWORD(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\IIS Extensions\\IIS AspNetCore Module V2\\Parameters", L"DisableANCM"); + + if (fDisableModule.has_value() && fDisableModule.value() != 0) { - DWORD dwType = 0; - DWORD dwData = 0; - DWORD cbData; - - cbData = sizeof(dwData); - if ((RegQueryValueEx(hKey, - L"DisableANCM", - nullptr, - &dwType, - (LPBYTE)&dwData, - &cbData) == NO_ERROR) && - (dwType == REG_DWORD)) - { - fDisableANCM = (dwData != 0); - } - - RegCloseKey(hKey); - } - - if (fDisableANCM) - { - // Logging - EventLog::Warn( - ASPNETCORE_EVENT_MODULE_DISABLED, - ASPNETCORE_EVENT_MODULE_DISABLED_MSG); + EventLog::Warn(ASPNETCORE_EVENT_MODULE_DISABLED, ASPNETCORE_EVENT_MODULE_DISABLED_MSG); // this will return 500 error to client // as we did not register the module return S_OK; diff --git a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/CommonLib.vcxproj b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/CommonLib.vcxproj index 066b433676..e6bb6ab200 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/CommonLib.vcxproj +++ b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/CommonLib.vcxproj @@ -225,6 +225,7 @@ + @@ -253,6 +254,7 @@ + diff --git a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/Environment.cpp b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/Environment.cpp index 7e5ff9ca47..be6484e16b 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/Environment.cpp +++ b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/Environment.cpp @@ -4,6 +4,7 @@ #include "Environment.h" #include +#include "exceptions.h" std::wstring Environment::ExpandEnvironmentVariables(const std::wstring & str) @@ -120,3 +121,22 @@ std::wstring Environment::GetDllDirectoryValue() return expandedStr; } + +bool Environment::IsRunning64BitProcess() +{ + // Check the bitness of the currently running process + // matches the dotnet.exe found. + BOOL fIsWow64Process = false; + THROW_LAST_ERROR_IF(!IsWow64Process(GetCurrentProcess(), &fIsWow64Process)); + + if (fIsWow64Process) + { + // 32 bit mode + return false; + } + + // Check the SystemInfo to see if we are currently 32 or 64 bit. + SYSTEM_INFO systemInfo; + GetNativeSystemInfo(&systemInfo); + return systemInfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64; +} diff --git a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/Environment.h b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/Environment.h index 8886ad428e..4f2611ef71 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/Environment.h +++ b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/Environment.h @@ -20,5 +20,7 @@ public: std::wstring GetCurrentDirectoryValue(); static std::wstring GetDllDirectoryValue(); + static + bool IsRunning64BitProcess(); }; diff --git a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/RegistryKey.cpp b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/RegistryKey.cpp new file mode 100644 index 0000000000..9f8bdeb409 --- /dev/null +++ b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/RegistryKey.cpp @@ -0,0 +1,55 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#include "RegistryKey.h" +#include "exceptions.h" + +std::optional RegistryKey::TryGetDWORD(HKEY section, const std::wstring& subSectionName, const std::wstring& valueName, DWORD flags) +{ + DWORD dwData = 0; + DWORD cbData = sizeof(dwData); + if (!CheckReturnValue(RegGetValue(section, subSectionName.c_str(), valueName.c_str(), RRF_RT_REG_DWORD | flags, nullptr, reinterpret_cast(&dwData), &cbData))) + { + return std::nullopt; + } + + return dwData; +} + +std::optional RegistryKey::TryGetString(HKEY section, const std::wstring& subSectionName, const std::wstring& valueName, DWORD flags) +{ + DWORD cbData; + + if (!CheckReturnValue(RegGetValue(section, subSectionName.c_str(), valueName.c_str(), RRF_RT_REG_SZ | flags, nullptr, nullptr, &cbData) != NO_ERROR)) + { + return std::nullopt; + } + + std::wstring data; + data.resize(cbData / sizeof(wchar_t)); + + if (!CheckReturnValue(RegGetValue(section, subSectionName.c_str(), valueName.c_str(), RRF_RT_REG_SZ | flags, nullptr, data.data(), &cbData) != NO_ERROR)) + { + return std::nullopt; + } + + data.resize(cbData / sizeof(wchar_t) - 1); + + return data; +} + +bool RegistryKey::CheckReturnValue(int errorCode) +{ + if (errorCode == NO_ERROR) + { + return true; + } + // NotFound result is expected, don't spam logs with failures + if (errorCode != ERROR_FILE_NOT_FOUND) + { + LOG_IF_FAILED(HRESULT_FROM_WIN32(errorCode)); + } + + return false; +} + diff --git a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/RegistryKey.h b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/RegistryKey.h new file mode 100644 index 0000000000..817e254f6f --- /dev/null +++ b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/RegistryKey.h @@ -0,0 +1,22 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +#include +#include "HandleWrapper.h" + +class RegistryKey +{ +public: + static + std::optional TryGetDWORD(HKEY section, const std::wstring& subSectionName, const std::wstring& valueName, DWORD flags = 0); + + static + std::optional TryGetString(HKEY section, const std::wstring& subSectionName, const std::wstring& valueName, DWORD flags = 0); + +private: + static + bool + CheckReturnValue(int errorCode); +}; diff --git a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/hostfxr_utility.cpp b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/hostfxr_utility.cpp index bc27526a41..f0be6e855b 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/hostfxr_utility.cpp +++ b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/hostfxr_utility.cpp @@ -10,6 +10,7 @@ #include "HandleWrapper.h" #include "Environment.h" #include "StringHelpers.h" +#include "RegistryKey.h" namespace fs = std::filesystem; @@ -257,6 +258,28 @@ HOSTFXR_UTILITY::GetAbsolutePathToDotnet( return dotnetViaWhere.value(); } + auto isWow64Process = Environment::IsRunning64BitProcess(); + const auto platform = isWow64Process? L"x64" : L"x86"; + + const auto installationLocation = RegistryKey::TryGetString( + HKEY_LOCAL_MACHINE, + std::wstring(L"SOFTWARE\\dotnet\\Setup\\InstalledVersions\\") + platform + L"\\sdk", + L"InstallLocation", + RRF_SUBKEY_WOW6432KEY); + + if (installationLocation.has_value()) + { + LOG_INFOF(L"InstallLocation registry key is set to '%ls'", installationLocation.value().c_str()); + + auto const installationLocationDotnet = fs::path(installationLocation.value()) / "dotnet.exe"; + + if (is_regular_file(installationLocationDotnet)) + { + LOG_INFOF(L"Found dotnet.exe in InstallLocation at '%ls'", installationLocationDotnet.c_str()); + return installationLocationDotnet; + } + } + const auto programFilesLocation = GetAbsolutePathToDotnetFromProgramFiles(); if (programFilesLocation.has_value()) { @@ -328,7 +351,6 @@ HOSTFXR_UTILITY::InvokeWhereToFindDotnet() HandleWrapper hThread; CComBSTR pwzDotnetName = NULL; DWORD dwFilePointer; - BOOL fIsWow64Process; BOOL fIsCurrentProcess64Bit; DWORD dwExitCode; STRU struDotnetSubstring; @@ -421,22 +443,7 @@ HOSTFXR_UTILITY::InvokeWhereToFindDotnet() LOG_INFOF(L"where.exe invocation returned: '%ls'", struDotnetLocationsString.QueryStr()); - // Check the bitness of the currently running process - // matches the dotnet.exe found. - FINISHED_LAST_ERROR_IF (!IsWow64Process(GetCurrentProcess(), &fIsWow64Process)); - - if (fIsWow64Process) - { - // 32 bit mode - fIsCurrentProcess64Bit = FALSE; - } - else - { - // Check the SystemInfo to see if we are currently 32 or 64 bit. - SYSTEM_INFO systemInfo; - GetNativeSystemInfo(&systemInfo); - fIsCurrentProcess64Bit = systemInfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64; - } + fIsCurrentProcess64Bit = Environment::IsRunning64BitProcess(); LOG_INFOF(L"Current process bitness type detected as isX64=%d", fIsCurrentProcess64Bit); diff --git a/src/Servers/IIS/IIS/test/Common.FunctionalTests/Inprocess/StartupTests.cs b/src/Servers/IIS/IIS/test/Common.FunctionalTests/Inprocess/StartupTests.cs index db1570b960..76c5482fba 100644 --- a/src/Servers/IIS/IIS/test/Common.FunctionalTests/Inprocess/StartupTests.cs +++ b/src/Servers/IIS/IIS/test/Common.FunctionalTests/Inprocess/StartupTests.cs @@ -13,7 +13,7 @@ using Microsoft.AspNetCore.Server.IIS.FunctionalTests.Utilities; using Microsoft.AspNetCore.Server.IntegrationTesting; using Microsoft.AspNetCore.Server.IntegrationTesting.IIS; using Microsoft.AspNetCore.Testing.xunit; -using Newtonsoft.Json; +using Microsoft.Win32; using Xunit; namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests @@ -107,6 +107,67 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests Assert.Equal(1, TestSink.Writes.Count(w => w.Message.Contains("Invoking where.exe to find dotnet.exe"))); } + [ConditionalTheory] + [InlineData(RuntimeArchitecture.x64)] + [InlineData(RuntimeArchitecture.x86)] + [RequiresNewShim] + [RequiresIIS(IISCapability.PoolEnvironmentVariables)] + public async Task StartsWithDotnetInstallLocation(RuntimeArchitecture runtimeArchitecture) + { + var deploymentParameters = _fixture.GetBaseDeploymentParameters(publish: true); + deploymentParameters.RuntimeArchitecture = runtimeArchitecture; + + // IIS doesn't allow empty PATH + deploymentParameters.EnvironmentVariables["PATH"] = "."; + deploymentParameters.WebConfigActionList.Add(WebConfigHelpers.AddOrModifyAspNetCoreSection("processPath", "dotnet")); + + // Key is always in 32bit view + using (var localMachine = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32)) + { + var installDir = DotNetCommands.GetDotNetInstallDir(runtimeArchitecture); + using (new TestRegistryKey( + localMachine, + "SOFTWARE\\dotnet\\Setup\\InstalledVersions\\" + runtimeArchitecture + "\\sdk", + "InstallLocation", + installDir)) + { + var deploymentResult = await DeployAsync(deploymentParameters); + await deploymentResult.AssertStarts(); + StopServer(); + // Verify that in this scenario dotnet.exe was found using InstallLocation lookup + // I would've liked to make a copy of dotnet directory in this test and use it for verification + // but dotnet roots are usually very large on dev machines so this test would take disproportionally long time and disk space + Assert.Equal(1, TestSink.Writes.Count(w => w.Message.Contains($"Found dotnet.exe in InstallLocation at '{installDir}\\dotnet.exe'"))); + } + } + } + + [ConditionalFact] + [RequiresIIS(IISCapability.PoolEnvironmentVariables)] + public async Task DoesNotStartIfDisabled() + { + var deploymentParameters = _fixture.GetBaseDeploymentParameters(publish: true); + + using (new TestRegistryKey( + Registry.LocalMachine, + "SOFTWARE\\Microsoft\\IIS Extensions\\IIS AspNetCore Module V2\\Parameters", + "DisableANCM", + 1)) + { + var deploymentResult = await DeployAsync(deploymentParameters); + // Disabling ANCM produces no log files + deploymentResult.AllowNoLogs(); + + var response = await deploymentResult.HttpClient.GetAsync("/HelloWorld"); + + Assert.False(response.IsSuccessStatusCode); + + StopServer(); + + EventLogHelpers.VerifyEventLogEvent(deploymentResult, "AspNetCore Module is disabled"); + } + } + public static TestMatrix TestVariants => TestMatrix.ForServers(DeployerSelector.ServerType) .WithTfms(Tfm.NetCoreApp30) diff --git a/src/Servers/IIS/IIS/test/Common.FunctionalTests/Utilities/TestRegistryKey.cs b/src/Servers/IIS/IIS/test/Common.FunctionalTests/Utilities/TestRegistryKey.cs new file mode 100644 index 0000000000..4ed7bf9c54 --- /dev/null +++ b/src/Servers/IIS/IIS/test/Common.FunctionalTests/Utilities/TestRegistryKey.cs @@ -0,0 +1,28 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.Win32; + +namespace Microsoft.AspNetCore.Server.IntegrationTesting +{ + public class TestRegistryKey : IDisposable + { + private readonly RegistryKey _baseHive; + private readonly RegistryKey _subKey; + private readonly string _keyName; + + public TestRegistryKey(RegistryKey baseHive, string keyName, string valueName, object value) + { + _baseHive = baseHive; + _keyName = keyName; + _subKey = baseHive.CreateSubKey(keyName); + _subKey.SetValue(valueName, value); + } + + public void Dispose() + { + _baseHive.DeleteSubKeyTree(_keyName, throwOnMissingSubKey: true); + } + } +} diff --git a/src/Servers/IIS/IntegrationTesting.IIS/src/IISDeployer.cs b/src/Servers/IIS/IntegrationTesting.IIS/src/IISDeployer.cs index 4f2e525e72..f5b5f14e10 100644 --- a/src/Servers/IIS/IntegrationTesting.IIS/src/IISDeployer.cs +++ b/src/Servers/IIS/IntegrationTesting.IIS/src/IISDeployer.cs @@ -325,6 +325,11 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting.IIS } + if (DeploymentParameters.RuntimeArchitecture == RuntimeArchitecture.x86) + { + pool.SetAttributeValue("enable32BitAppOnWin64", "true");; + } + RunServerConfigActions(config, contentRoot); } diff --git a/src/Servers/IIS/tools/SetupTestEnvironment.ps1 b/src/Servers/IIS/tools/SetupTestEnvironment.ps1 index 3adaf79045..36b98a8bf7 100644 --- a/src/Servers/IIS/tools/SetupTestEnvironment.ps1 +++ b/src/Servers/IIS/tools/SetupTestEnvironment.ps1 @@ -1,5 +1,11 @@ param($Mode) +# TEMP TEMP TEMP +# While doing https://github.com/aspnet/AspNetCore/pull/5705 I accidentally disabled ANCM on CI machines using +# the registy key. Remove it to allow tests to pass + +Remove-Item "HKLM:\SOFTWARE\Microsoft\IIS Extensions\IIS AspNetCore Module V2\Parameters" -ErrorAction Ignore; + $DumpFolder = "$env:ASPNETCORE_TEST_LOG_DIR\dumps" if (!($DumpFolder)) { @@ -70,7 +76,7 @@ function Setup-Dumps() New-Item -Path $werHive -Name LocalDumps } - Move-Item $env:windir\System32\vsjitdebugger.exe $env:windir\System32\_vsjitdebugger.exe; + Move-Item $env:windir\System32\vsjitdebugger.exe $env:windir\System32\_vsjitdebugger.exe -ErrorAction Ignore; New-ItemProperty $werHive -Name "DontShowUI" -Value 1 -PropertyType "DWORD" -Force;