| 1 | /*++ |
| 2 | |
| 3 | Copyright (c) Microsoft. All rights reserved. |
| 4 | |
| 5 | Module Name: |
| 6 | |
| 7 | ServiceMain.cpp |
| 8 | |
| 9 | Abstract: |
| 10 | |
| 11 | This file contains the entrypoint for the Lxss Manager service. |
| 12 | |
| 13 | --*/ |
| 14 | |
| 15 | #include "precomp.h" |
| 16 | #include "comservicehelper.h" |
| 17 | #include "LxssSecurity.h" |
| 18 | #include "WslCoreFilesystem.h" |
| 19 | #include "LxssIpTables.h" |
| 20 | #include "LxssUserSessionFactory.h" |
| 21 | #include "WSLCSessionManagerFactory.h" |
| 22 | #include <ctime> |
| 23 | |
| 24 | using namespace wsl::windows::common::registry; |
| 25 | using namespace wsl::windows::common::string; |
| 26 | using namespace wsl::windows::common::timestamp; |
| 27 | using namespace wsl::windows::common::wslutil; |
| 28 | using namespace wsl::windows::policies; |
| 29 | |
| 30 | bool g_lxcoreInitialized{false}; |
| 31 | wil::unique_event g_networkingReady{wil::EventOptions::ManualReset}; |
| 32 | |
| 33 | wsl::windows::service::PluginManager g_pluginManager; |
| 34 | |
| 35 | // Declare the LxssUserSession COM class. |
| 36 | CoCreatableClassWrlCreatorMapInclude(LxssUserSession); |
| 37 | |
| 38 | // Declare the WSLCSessionManager COM class. |
| 39 | CoCreatableClassWrlCreatorMapInclude(WSLCSessionManager); |
| 40 | |
| 41 | struct WslServiceSecurityPolicy |
| 42 | { |
| 43 | static LPCWSTR GetSDDLText() |
| 44 | { |
| 45 | // COM Access and Launch permissions allowed for authenticated user, principal self, and system. |
| 46 | // 0xB = (COM_RIGHTS_EXECUTE | COM_RIGHTS_EXECUTE_LOCAL | COM_RIGHTS_ACTIVATE_LOCAL) |
| 47 | // N.B. This should be kept in sync with the security descriptors in the appxmanifest and package.wix. |
| 48 | return L"O:BAG:BAD:(A;;0xB;;;AU)(A;;0xB;;;PS)(A;;0xB;;;SY)"; |
| 49 | } |
| 50 | }; |
| 51 | |
| 52 | class WslService : public Windows::Internal::Service<WslService, Windows::Internal::ContinueRunningWithNoObjects, WslServiceSecurityPolicy> |
| 53 | { |
| 54 | public: |
| 55 | static wchar_t* GetName() |
| 56 | { |
| 57 | return const_cast<LPWSTR>(L"WslService"); |
| 58 | } |
| 59 | |
| 60 | static void OnSessionChanged(DWORD eventType, DWORD sessionId); |
| 61 | HRESULT OnServiceStarting(); |
| 62 | HRESULT ServiceStarted(); |
| 63 | void ServiceStopped(); |
| 64 | |
| 65 | private: |
| 66 | static void __stdcall CheckForUpdates(_Inout_ PTP_CALLBACK_INSTANCE, _Inout_ PVOID Context, _Inout_ PTP_TIMER); |
| 67 | static void ApplyProcessPolicies(); |
| 68 | |
| 69 | void CreateExplorerExtensions() noexcept; |
| 70 | void EvaluateWslPolicy(); |
| 71 | void Initialize(); |
| 72 | static void InitializePlan9Redirector(); |
| 73 | void RegisterEventSource(); |
| 74 | void StartCheckingForUpdates(); |
| 75 | |
| 76 | wil::unique_couninitialize_call m_coInit{false}; |
| 77 | wil::unique_registry_watcher m_watcher; |
| 78 | wil::unique_threadpool_timer m_updateCheckTimer; |
| 79 | wil::unique_any_handle_null<decltype(&::DeregisterEventSource), ::DeregisterEventSource> m_eventLog; |
| 80 | }; |
| 81 | |
| 82 | void WslService::EvaluateWslPolicy() |
| 83 | { |
| 84 | // If WSL is disabled, terminate any sessions and block future sessions from being created. |
| 85 | // |
| 86 | // N.B. This is done instead of failing service start so a proper error can be returned to the user. |
| 87 | const auto policiesKey = OpenPoliciesKey(); |
| 88 | const auto enabled = IsFeatureAllowed(policiesKey.get(), c_allowWSL); |
| 89 | if (enabled) |
| 90 | { |
| 91 | Initialize(); |
| 92 | } |
| 93 | |
| 94 | SetSessionPolicy(enabled); |
| 95 | } |
| 96 | |
| 97 | void WslService::Initialize() |
| 98 | { |
| 99 | static std::once_flag flag{}; |
| 100 | std::call_once(flag, [&]() { |
| 101 | // Initialize the connection to the LxCore driver. |
| 102 | // |
| 103 | // N.B. The WSL optional component is required on Windows 10. On Windows 11 and later, |
| 104 | // the lifted WSL service can run but will only support WSL2 distros. |
| 105 | g_lxcoreInitialized = NT_SUCCESS(::LxssClientInitialize()); |
| 106 | |
| 107 | try |
| 108 | { |
| 109 | // Initialize the Plan 9 redirector (can fail iff the OC is not enabled on Win10). |
| 110 | // Failures here are silently ignored because we don't want the service to fail to start in that case |
| 111 | // so it can return WSL_E_WSL_OPTIONAL_COMPONENT_REQUIRED in LxssUserSession |
| 112 | InitializePlan9Redirector(); |
| 113 | } |
| 114 | CATCH_LOG() |
| 115 | |
| 116 | RegisterEventSource(); |
| 117 | }); |
| 118 | } |
| 119 | |
| 120 | void WslService::InitializePlan9Redirector() |
| 121 | { |
| 122 | // Make sure that the Plan 9 redirector trigger start prefix is correct. |
| 123 | try |
| 124 | { |
| 125 | // Acquire backup and restore privileges to modify the P9NP trigger start registry key. |
| 126 | auto restore = wsl::windows::common::security::AcquirePrivileges({SE_BACKUP_NAME, SE_RESTORE_NAME}); |
| 127 | |
| 128 | // Read the P9NP registry key and ensure it contains the correct value. |
| 129 | constexpr auto* keyName = L"SYSTEM\\CurrentControlSet\\Services\\P9NP\\NetworkProvider"; |
| 130 | const auto key = CreateKey(HKEY_LOCAL_MACHINE, keyName, (KEY_READ | KEY_SET_VALUE), nullptr, REG_OPTION_BACKUP_RESTORE); |
| 131 | constexpr auto* valueName = L"TriggerStartPrefix"; |
| 132 | DWORD valueType{}; |
| 133 | THROW_IF_WIN32_ERROR(RegGetValueW(key.get(), nullptr, valueName, (RRF_RT_ANY | RRF_NOEXPAND), &valueType, nullptr, nullptr)); |
| 134 | if (valueType != REG_MULTI_SZ) |
| 135 | { |
| 136 | // Because older Windows 10 builds won't have the p9rdr changes to support TriggerStartPrefix being a REG_MULTI_SZ, |
| 137 | // make sure that this build has the updated AppIdFlags value (added to support vp9fs being called from packaged context), |
| 138 | // which was added in the same commit. |
| 139 | // This theoretically shouldn't happen since the package shouldn't install on Windows 10 builds that are too old to |
| 140 | // support lifted, but if this block ran on such a build it would completely break p9rdr, so better safe than sorry. |
| 141 | if (!wsl::windows::common::helpers::IsWindows11OrAbove()) |
| 142 | { |
| 143 | auto appIdFlags = ReadDword(HKEY_CLASSES_ROOT, L"AppID\\{DFB65C4C-B34F-435D-AFE9-A86218684AA8}", L"AppIdFlags", 0); |
| 144 | THROW_HR_IF_MSG( |
| 145 | E_UNEXPECTED, |
| 146 | WI_IsFlagClear(appIdFlags, APPIDREGFLAGS_AAA_NO_IMPLICIT_ACTIVATE_AS_IU), |
| 147 | "TriggerStartPrefix needs update, but AppIdFlags isn't up to date"); |
| 148 | } |
| 149 | |
| 150 | WSL_LOG("Updating TriggerStartPrefix", TraceLoggingLevel(WINEVENT_LEVEL_INFO)); |
| 151 | |
| 152 | constexpr wchar_t newValue[] = L"wsl.localhost\0wsl$\0"; |
| 153 | THROW_IF_WIN32_ERROR(RegSetValueEx(key.get(), valueName, 0, REG_MULTI_SZ, (BYTE*)newValue, sizeof(newValue))); |
| 154 | } |
| 155 | } |
| 156 | CATCH_LOG() |
| 157 | |
| 158 | // Make sure the Plan 9 redirector driver is loaded. |
| 159 | wsl::windows::common::redirector::EnsureRedirectorStarted(); |
| 160 | } |
| 161 | |
| 162 | HRESULT WslService::OnServiceStarting() |
| 163 | try |
| 164 | { |
| 165 | ConfigureCrt(); |
| 166 | |
| 167 | // Enable contextualized errors |
| 168 | wsl::windows::common::EnableContextualizedErrors(true, false, true); |
| 169 | |
| 170 | // Initialize telemetry. |
| 171 | WslTraceLoggingInitialize(WslServiceTelemetryProvider, !wsl::shared::OfficialBuild); |
| 172 | |
| 173 | WSL_LOG("Service starting", TraceLoggingLevel(WINEVENT_LEVEL_INFO)); |
| 174 | |
| 175 | // Don't kill the process on unknown C++ exceptions. |
| 176 | wil::g_fResultFailFastUnknownExceptions = false; |
| 177 | |
| 178 | wsl::windows::common::security::ApplyProcessMitigationPolicies(); |
| 179 | |
| 180 | // Initialize Winsock. |
| 181 | WSADATA Data; |
| 182 | THROW_IF_WIN32_ERROR(WSAStartup(MAKEWORD(2, 2), &Data)); |
| 183 | |
| 184 | // Load plugins. |
| 185 | g_pluginManager.LoadPlugins(); |
| 186 | |
| 187 | // Check if WSL is disabled via policy and set up a registry watcher to watch for changes. |
| 188 | // |
| 189 | // N.B. The registry watcher must be created before checking the policy to avoid missing notifications. |
| 190 | m_watcher = wil::make_registry_watcher(HKEY_LOCAL_MACHINE, ROOT_POLICIES_KEY, true, [this](wil::RegistryChangeKind) { |
| 191 | try |
| 192 | { |
| 193 | EvaluateWslPolicy(); |
| 194 | } |
| 195 | CATCH_LOG() |
| 196 | }); |
| 197 | |
| 198 | EvaluateWslPolicy(); |
| 199 | |
| 200 | wsl::windows::common::helpers::RegisterWithDcat(); |
| 201 | |
| 202 | return S_OK; |
| 203 | } |
| 204 | CATCH_RETURN() |
| 205 | |
| 206 | void WslService::RegisterEventSource() |
| 207 | try |
| 208 | { |
| 209 | m_eventLog.reset(::RegisterEventSource(nullptr, L"WSL")); |
| 210 | THROW_LAST_ERROR_IF(!m_eventLog); |
| 211 | |
| 212 | wsl::windows::common::SetEventLog(m_eventLog.get()); |
| 213 | } |
| 214 | CATCH_LOG(); |
| 215 | |
| 216 | HRESULT WslService::ServiceStarted() |
| 217 | { |
| 218 | m_coInit = wil::CoInitializeEx(COINIT_MULTITHREADED); |
| 219 | |
| 220 | // Cleanup any data from a previously aborted session (crash, power loss, etc). |
| 221 | LxssIpTables::CleanupRemnants(); |
| 222 | g_networkingReady.SetEvent(); |
| 223 | |
| 224 | if constexpr (wsl::shared::OfficialBuild) |
| 225 | { |
| 226 | StartCheckingForUpdates(); |
| 227 | } |
| 228 | |
| 229 | return S_OK; |
| 230 | } |
| 231 | |
| 232 | void WslService::OnSessionChanged(DWORD eventType, DWORD sessionId) |
| 233 | { |
| 234 | if (eventType == WTS_SESSION_LOGOFF) |
| 235 | { |
| 236 | TerminateSession(sessionId); |
| 237 | } |
| 238 | } |
| 239 | |
| 240 | void WslService::ServiceStopped() |
| 241 | { |
| 242 | WSL_LOG("Service stopping", TraceLoggingLevel(WINEVENT_LEVEL_INFO)); |
| 243 | |
| 244 | // Stop checking for updates. |
| 245 | m_updateCheckTimer.reset(); |
| 246 | |
| 247 | // Stop watching the WSL policy registry keys. |
| 248 | m_watcher.reset(); |
| 249 | |
| 250 | // Terminate all user sessions. |
| 251 | ClearSessionsAndBlockNewInstances(); |
| 252 | |
| 253 | // Also tear down WSLC sessions. |
| 254 | wsl::windows::service::wslc::ClearWslcSessionsAndBlockNewInstances(); |
| 255 | |
| 256 | // Disconnect from the LxCore driver. |
| 257 | if (g_lxcoreInitialized) |
| 258 | { |
| 259 | LxssClientUninitialize(); |
| 260 | } |
| 261 | |
| 262 | // There is a potential deadlock if CoUninitialize() is called before the LanguageChangeNotifyThread |
| 263 | // isn't done initializing. Clearing the COM objects before calling CoUninitialize() works around the issue. |
| 264 | winrt::clear_factory_cache(); |
| 265 | |
| 266 | // Tear down telemetry. |
| 267 | WslTraceLoggingUninitialize(); |
| 268 | |
| 269 | // uninitialize COM. This must be done here because this call can cause cleanups that will be fail |
| 270 | // if the CRT is shutting down. |
| 271 | m_coInit.reset(); |
| 272 | } |
| 273 | |
| 274 | void WslService::StartCheckingForUpdates() |
| 275 | try |
| 276 | { |
| 277 | const auto lxssKey = OpenLxssMachineKey(KEY_QUERY_VALUE); |
| 278 | constexpr std::uint64_t c_updateCheckPeriodDefaultMs = 24 * 60 * 60 * 1000; // 24h |
| 279 | const auto period = |
| 280 | wsl::windows::common::registry::ReadDword(lxssKey.get(), nullptr, L"UpdateCheckPeriodMs", c_updateCheckPeriodDefaultMs); |
| 281 | |
| 282 | if (period <= 0) |
| 283 | { |
| 284 | WSL_LOG("Update check is disabled via the registry", TraceLoggingLevel(WINEVENT_LEVEL_INFO)); |
| 285 | |
| 286 | return; |
| 287 | } |
| 288 | |
| 289 | m_updateCheckTimer.reset(CreateThreadpoolTimer(WslService::CheckForUpdates, this, nullptr)); |
| 290 | THROW_LAST_ERROR_IF_NULL(m_updateCheckTimer); |
| 291 | |
| 292 | // Check for updates at the configured period, starting one minute after the service starts. |
| 293 | auto dueTime = wil::filetime::from_int64(static_cast<ULONGLONG>(-1 * wil::filetime_duration::one_minute)); |
| 294 | SetThreadpoolTimer(m_updateCheckTimer.get(), &dueTime, period, 60 * 1000); |
| 295 | } |
| 296 | CATCH_LOG() |
| 297 | |
| 298 | void WslService::CheckForUpdates(_Inout_ PTP_CALLBACK_INSTANCE, _Inout_ PVOID Context, _Inout_ PTP_TIMER) |
| 299 | try |
| 300 | { |
| 301 | auto [version, _] = GetLatestGitHubRelease(false); |
| 302 | if (ParseWslPackageVersion(version) > ParseWslPackageVersion(TEXT(WSL_PACKAGE_VERSION))) |
| 303 | { |
| 304 | WSL_LOG("WSL Package update is available", TraceLoggingLevel(WINEVENT_LEVEL_INFO)); |
| 305 | |
| 306 | // Reset the timer since there's no reason to check for updates anymore. |
| 307 | SetThreadpoolTimer(static_cast<WslService*>(Context)->m_updateCheckTimer.get(), nullptr, 0, 0); |
| 308 | |
| 309 | // Get current release date |
| 310 | const std::wstring currentReleaseCreatedAtDate = GetGitHubReleaseByTag(TEXT(WSL_PACKAGE_VERSION)).created_at; |
| 311 | |
| 312 | const auto tp = std::chrono::system_clock::from_time_t( |
| 313 | static_cast<std::time_t>(Rfc3339ToEpoch(WideToMultiByte(currentReleaseCreatedAtDate)))); |
| 314 | |
| 315 | // If their release of WSL is older than 30 days, then show a notification to update |
| 316 | if (std::chrono::system_clock::now() - std::chrono::days(30) > tp) |
| 317 | { |
| 318 | // Create a notification to inform the user that an update is available |
| 319 | THROW_IF_FAILED(wsl::windows::common::notifications::DisplayUpdateNotification(version)); |
| 320 | |
| 321 | WSL_LOG("WSL Package update notification displayed", TraceLoggingLevel(WINEVENT_LEVEL_INFO)); |
| 322 | } |
| 323 | } |
| 324 | } |
| 325 | CATCH_LOG() |
| 326 | |
| 327 | int __cdecl wmain() |
| 328 | { |
| 329 | WslService::ProcessMain(); |
| 330 | return 0; |
| 331 | } |