WSL/src/windows/service/exe/LxssInstance.cpp
Ben Hillis 87c1100620
.clang-format: add InsertBraces: true and minor fix to FormatSource.ps1 (#13712)
Co-authored-by: Ben Hillis <benhill@ntdev.microsoft.com>
2025-11-14 16:12:02 -08:00

883 lines
33 KiB
C++

/*++
Copyright (c) Microsoft. All rights reserved.
Module Name:
LxssInstance.cpp
Abstract:
This file contains lxss instance definitions.
--*/
#include "precomp.h"
#include "LxssInstance.h"
#include "LxssSecurity.h"
#include "LxssUserSession.h"
// Number of milliseconds to wait for the init process to respond.
#define LXSS_INIT_CONNECTION_TIMEOUT_MS (30 * 1000)
// Registry keys related to integrity level checks.
#define LXSS_SERVICE_REGISTRY_INTEGRITY_CHECK L"DisableMixedIntegrityLaunch"
#define LXSS_SERVICE_REGISTRY_INTEGRITY_CHECK_DISABLED 0
#define LXSS_SERVICE_REGISTRY_INTEGRITY_CHECK_ENABLED 1
// Legacy folder mount information.
#define LXSS_CACHE_MOUNT_LXSS "/cache"
#define LXSS_CACHE_MOUNT_NT L"cache"
#define LXSS_CACHE_PERMISSIONS (0770)
#define LXSS_DATA_MOUNT_LXSS "/data"
#define LXSS_DATA_MOUNT_NT L"data"
#define LXSS_DATA_PERMISSIONS (0771)
#define LXSS_HOME_MOUNT_LXSS "/home"
#define LXSS_HOME_MOUNT_NT L"home"
#define LXSS_HOME_PERMISSIONS (0755)
#define LXSS_MNT_MOUNT_LXSS "/mnt"
#define LXSS_MNT_MOUNT_NT L"mnt"
#define LXSS_MNT_PERMISSIONS (0755)
#define LXSS_ROOT_HOME_MOUNT_LXSS "/root"
#define LXSS_ROOT_HOME_MOUNT_NT L"root"
#define LXSS_ROOT_PERMISSIONS (0700)
#define LXSS_ROOTFS_PERMISSIONS (0755)
extern bool g_lxcoreInitialized;
using namespace std::placeholders;
using namespace Microsoft::WRL;
using namespace wsl::windows::common::filesystem;
LxssInstance::LxssInstance(
_In_ const GUID& InstanceId,
_In_ const LXSS_DISTRO_CONFIGURATION& Configuration,
_In_ ULONG DefaultUid,
_In_ ULONG64 ClientLifetimeId,
_In_ const std::function<void()>& TerminationCallback,
_In_ const std::function<void()>& UpdateInitCallback,
_In_ ULONG Flags,
_In_ int IdleTimeout) :
LxssRunningInstance(IdleTimeout),
m_instanceId(InstanceId),
m_instanceHandle(),
m_terminationCallback(TerminationCallback),
m_initialized(false),
m_running(false),
m_defaultUid(DefaultUid),
m_configuration(Configuration),
m_ntClientLifetimeId(ClientLifetimeId),
m_instanceBasicIntegrityLevelCheckEnabled(false),
m_redirectorConnectionTargets{m_configuration.Name}
{
// Running a WSL1 distro is not possible if the lxcore driver is not present.
THROW_HR_IF(WSL_E_WSL1_NOT_SUPPORTED, !g_lxcoreInitialized);
// Copy immutable distribution data into the info structure.
m_distributionInfo.Id = m_configuration.DistroId;
m_distributionInfo.Name = m_configuration.Name.c_str();
m_distributionInfo.PackageFamilyName = m_configuration.PackageFamilyName.c_str();
m_distributionInfo.InitPid = 1;
m_ServerPort = std::make_shared<LxssServerPort>();
{
// Create a job to hold pico processes from LXSS.
auto runAsSessionUser = wil::CoImpersonateClient();
m_instanceJob.reset(CreateJobObjectW(nullptr, nullptr));
THROW_LAST_ERROR_IF(!m_instanceJob);
Security::InitializeInstanceJob(m_instanceJob.get());
// Check if VPN detection is enabled.
const wil::unique_hkey LxssKey = wsl::windows::common::registry::OpenLxssUserKey();
m_enableVpnDetection = (wsl::windows::common::registry::ReadDword(LxssKey.get(), nullptr, L"EnableVpnDetection", 1)) != 0;
}
// Store the user token for access checks.
m_userToken = wsl::windows::common::security::GetUserToken(TokenImpersonation);
// Create manual reset event that is signaled on instance termination
m_instanceTerminatedEvent.reset(CreateEventW(nullptr, TRUE, FALSE, nullptr));
THROW_LAST_ERROR_IF(!m_instanceTerminatedEvent);
// Check if integrity level check is enabled.
//
// N.B. The registry value is only writable from high-IL and above.
if (wsl::windows::common::registry::ReadDword(
HKEY_LOCAL_MACHINE, LXSS_SERVICE_REGISTRY_PATH, LXSS_SERVICE_REGISTRY_INTEGRITY_CHECK, LXSS_SERVICE_REGISTRY_INTEGRITY_CHECK_DISABLED) ==
LXSS_SERVICE_REGISTRY_INTEGRITY_CHECK_ENABLED)
{
// Query the integrity level of the caller and store it in the instance.
m_instanceBasicIntegrityLevel = wsl::windows::common::security::GetUserBasicIntegrityLevel(m_userToken.get());
m_instanceBasicIntegrityLevelCheckEnabled = true;
}
// Initialize mount paths.
_ConfigureFilesystem(Flags);
// Update the init binary if needed.
UpdateInitCallback();
// Create the LXSS instance
_StartInstance(Configuration.Flags);
// Create a threadpool wait object to listen for instance termination
m_terminationWait.reset(CreateThreadpoolWait(
[](PTP_CALLBACK_INSTANCE, PVOID context, PTP_WAIT, TP_WAIT_RESULT) {
try
{
const auto instance = static_cast<LxssInstance*>(context);
instance->OnTerminated();
}
CATCH_LOG()
},
this,
nullptr));
THROW_LAST_ERROR_IF(!m_terminationWait);
SetThreadpoolWait(m_terminationWait.get(), m_instanceTerminatedEvent.get(), nullptr);
// Mark the instance as started.
m_running = true;
}
LxssInstance::~LxssInstance()
{
Stop();
}
bool CreateLxProcessIsValidStdHandle(_In_ PLXSS_HANDLE StdHandle)
{
bool IsValid = false;
switch (StdHandle->HandleType)
{
case LxssHandleConsole:
if (StdHandle->Handle == LXSS_HANDLE_USE_CONSOLE)
{
IsValid = true;
}
break;
case LxssHandleInput:
case LxssHandleOutput:
if (StdHandle->Handle != LXSS_HANDLE_USE_CONSOLE)
{
IsValid = true;
}
break;
}
return IsValid;
}
void LxssInstance::CreateLxProcess(
_In_ const CreateLxProcessData& CreateProcessData,
_In_ const CreateLxProcessContext& CreateProcessContext,
_In_ const CreateLxProcessConsoleData& ConsoleData,
_In_ SHORT Columns,
_In_ SHORT Rows,
_In_ PLXSS_STD_HANDLES StdHandles,
_Out_ GUID* InstanceId,
_Out_ HANDLE* ProcessHandle,
_Out_ HANDLE* ServerHandle,
_Out_ HANDLE* StandardIn,
_Out_ HANDLE* StandardOut,
_Out_ HANDLE* StandardErr,
_Out_ HANDLE* CommunicationChannel,
_Out_ HANDLE* InteropSocket)
{
UNREFERENCED_PARAMETER(Columns);
UNREFERENCED_PARAMETER(Rows);
THROW_HR_IF(E_INVALIDARG, !CreateLxProcessIsValidStdHandle(&StdHandles->StdIn));
THROW_HR_IF(E_INVALIDARG, !CreateLxProcessIsValidStdHandle(&StdHandles->StdOut));
THROW_HR_IF(E_INVALIDARG, !CreateLxProcessIsValidStdHandle(&StdHandles->StdErr));
// Check that the process is at the same basic integrity level if
// needed.
if (m_instanceBasicIntegrityLevelCheckEnabled != false)
{
const DWORD BasicIntegrityLevel =
wsl::windows::common::security::GetUserBasicIntegrityLevel(CreateProcessContext.UserToken.get());
if (m_instanceBasicIntegrityLevel != BasicIntegrityLevel)
{
if (m_instanceBasicIntegrityLevel > BasicIntegrityLevel)
{
THROW_HR(WSL_E_LOWER_INTEGRITY);
}
THROW_HR(WSL_E_HIGHER_INTEGRITY);
}
}
if (m_oobeCompleteEvent && !m_oobeCompleteEvent.is_signaled())
{
EMIT_USER_WARNING(wsl::shared::Localization::MessageWaitingForOobe(m_configuration.Name.c_str()));
m_oobeCompleteEvent.wait();
}
// Duplicate the handles from the calling process into the current process.
std::vector<wil::unique_handle> StdHandlesLocal(3);
if (StdHandles->StdIn.Handle != LXSS_HANDLE_USE_CONSOLE)
{
StdHandlesLocal[0].reset(wsl::windows::common::wslutil::DuplicateHandleFromCallingProcess(ULongToHandle(StdHandles->StdIn.Handle)));
}
if (StdHandles->StdOut.Handle != LXSS_HANDLE_USE_CONSOLE)
{
StdHandlesLocal[1].reset(wsl::windows::common::wslutil::DuplicateHandleFromCallingProcess(ULongToHandle(StdHandles->StdOut.Handle)));
}
if (StdHandles->StdErr.Handle != LXSS_HANDLE_USE_CONSOLE)
{
StdHandlesLocal[2].reset(wsl::windows::common::wslutil::DuplicateHandleFromCallingProcess(ULongToHandle(StdHandles->StdErr.Handle)));
}
// Enable symlink creation privilege on the token, which is needed
// to allow DrvFs to create NT symlinks.
//
// N.B. This privilege is required to create NT symlinks only when
// developer mode is disabled. When developer mode is enabled,
// acquiring the privilege can fail, but creating the symlink
// will succeed even without the privilege. Therefore, it does
// not matter if this call fails.
const wil::unique_handle Token = wsl::windows::common::security::GetUserToken(TokenPrimary);
wsl::windows::common::security::EnableTokenPrivilege(Token.get(), SE_CREATE_SYMBOLIC_LINK_NAME);
// Create an unnamed server port if the caller provided a buffer and
// interop is enabled.
wil::unique_handle ServerPort;
PHANDLE ServerPortPointer = nullptr;
if (LXSS_INTEROP_ENABLED(CreateProcessContext.Flags))
{
ServerPortPointer = &ServerPort;
}
// Send the create process request to the session leader.
bool CreatedSessionLeader;
const auto SessionLeader = std::static_pointer_cast<LxssMessagePort>(
m_consoleManager->GetSessionLeader(ConsoleData, CreateProcessContext.Elevated, &CreatedSessionLeader));
// If the session leader was just created, ensure the networking information
// is up-to-date.
if (CreatedSessionLeader)
{
_UpdateNetworkConfigurationFiles(true);
}
wil::unique_handle ProcessHandleLocal =
_CreateLxProcess(SessionLeader, CreateProcessData, StdHandlesLocal, Token, m_defaultUid, ServerPortPointer);
*InstanceId = m_instanceId;
*ProcessHandle = ProcessHandleLocal.release();
*ServerHandle = ServerPort ? ServerPort.release() : nullptr;
*StandardIn = nullptr;
*StandardOut = nullptr;
*StandardErr = nullptr;
*CommunicationChannel = nullptr;
*InteropSocket = nullptr;
}
ULONG LxssInstance::GetClientId() const
{
return LXSS_CLIENT_ID_INVALID;
}
GUID LxssInstance::GetDistributionId() const
{
return m_configuration.DistroId;
}
std::shared_ptr<LxssPort> LxssInstance::GetInitPort()
{
return m_InitMessagePort;
}
void LxssInstance::UpdateTimezone()
{
const auto timezone = wsl::windows::common::helpers::GetLinuxTimezone(m_userToken.get());
auto message = wsl::windows::common::helpers::GenerateTimezoneUpdateMessage(timezone);
auto lock = m_InitMessagePort->Lock();
m_InitMessagePort->Send(message.data(), gsl::narrow_cast<ULONG>(message.size()));
}
ULONG64 LxssInstance::GetLifetimeManagerId() const
{
return m_ntClientLifetimeId;
}
void LxssInstance::Initialize()
{
std::lock_guard<std::mutex> lock(m_stateLock);
if (m_initialized)
{
return;
}
const auto socketPath = m_configuration.BasePath / LXSS_PLAN9_UNIX_SOCKET;
// Open the communication channel with init.
_InitiateConnectionToInitProcess();
// Send initial configuration information to the init daemon.
_InitializeConfiguration(socketPath);
// Initialize networking for the instance, this will also register for network
// state change notifications and send the initial network information to the
// init process.
_InitializeNetworking();
// Initialization successful.
m_initialized = true;
}
void LxssInstance::OnTerminated() const
{
m_terminationCallback();
}
bool LxssInstance::RequestStop(_In_ bool Force)
{
std::lock_guard<std::mutex> lock(m_stateLock);
// Send the message to the init daemon to check if the instance can be terminated.
bool shutdown = true;
if (m_InitMessagePort)
{
try
{
auto lock = m_InitMessagePort->Lock();
LX_INIT_TERMINATE_INSTANCE terminateMessage{};
terminateMessage.Header.MessageType = LxInitMessageTerminateInstance;
terminateMessage.Header.MessageSize = sizeof(terminateMessage);
terminateMessage.Force = Force;
m_InitMessagePort->Send(&terminateMessage, sizeof(terminateMessage));
LX_INIT_TERMINATE_INSTANCE::TResponse terminateResponse{};
m_InitMessagePort->Receive(&terminateResponse, sizeof(terminateResponse));
shutdown = terminateResponse.Result;
}
CATCH_LOG()
}
return shutdown;
}
void LxssInstance::Stop()
{
std::lock_guard<std::mutex> lock(m_stateLock);
// Do nothing if the instance is already terminated.
if (!m_running)
{
return;
}
// Logs when a distro is stopped
WSL_LOG_TELEMETRY(
"StopInstance",
PDT_ProductAndServiceUsage,
TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA),
TraceLoggingValue(m_configuration.Name.c_str(), "distroName"),
TraceLoggingValue(LXSS_WSL_VERSION_1, "version"),
TraceLoggingValue(m_instanceId, "instanceId"));
// Unregister the instance termination TP wait.
if (m_terminationWait)
{
SetThreadpoolWait(m_terminationWait.get(), nullptr, nullptr);
}
// Unregister from network change notifications.
m_networkNotificationHandle.reset();
// Unwind in the reverse order of creation. Stop the LXSS instance, delete it.
LOG_IF_NTSTATUS_FAILED(::LxssClientInstanceStop(m_instanceHandle.get()));
m_instanceTerminatedEvent.reset();
LOG_IF_NTSTATUS_FAILED(::LxssClientInstanceDestroy(m_instanceHandle.get()));
m_instanceHandle.reset();
// Wait on the oobe thread.
if (m_oobeThread.joinable())
{
m_oobeThread.join();
}
// Remove the instance's Plan 9 Redirector connection targets.
m_redirectorConnectionTargets.RemoveAll();
// Attempt to clean up the instance's temp folder.
if (!m_tempPath.empty())
{
auto runAsUser = wil::impersonate_token(m_userToken.get());
LOG_IF_FAILED(wil::RemoveDirectoryRecursiveNoThrow(m_tempPath.c_str()));
}
m_running = false;
m_tempPath.clear();
m_rootDirectory.reset();
m_tempDirectory.reset();
return;
}
void LxssInstance::RegisterPlan9ConnectionTarget(_In_ HANDLE userToken)
{
const auto path = m_configuration.BasePath / LXSS_PLAN9_UNIX_SOCKET;
using unique_unicode_string = wil::unique_struct<UNICODE_STRING, decltype(RtlFreeUnicodeString), RtlFreeUnicodeString>;
unique_unicode_string socketPath;
THROW_IF_NTSTATUS_FAILED(RtlDosPathNameToNtPathName_U_WithStatus(path.c_str(), &socketPath, nullptr, nullptr));
m_redirectorConnectionTargets.AddConnectionTarget(
userToken, {}, m_defaultUid, std::wstring_view{socketPath.Buffer, socketPath.Length / sizeof(WCHAR)});
}
const WSLDistributionInformation* LxssInstance::DistributionInformation() const noexcept
{
return &m_distributionInfo;
}
void LxssInstance::_ConfigureFilesystem(_In_ ULONG Flags)
{
// Part of this process will try to upgrade existing LxFs folders to
// enable the per-directory case sensitivity flag. To allow easy detection
// of already processed folders, and resumption in case the process is
// interrupted, directories are only marked case-sensitive after their
// children are processed.
//
// Paths for LXSS instances look like so:
//
// <root>
// \rootfs <-- Where the file system is located
// \temp\{Instance GUID} <-- Where temporary files go
auto runAsUser = wil::CoImpersonateClient();
// Ensure the parent temp folder exists and is empty.
const auto tempFolder = m_configuration.BasePath / LXSS_TEMP_DIRECTORY;
EnsureDirectory(tempFolder.c_str());
LOG_IF_FAILED(wil::RemoveDirectoryRecursiveNoThrow(tempFolder.c_str(), wil::RemoveDirectoryOptions::KeepRootDirectory));
// Create a subdirectory to be used as the temp folder for this instance.
const auto instanceIdString = wsl::shared::string::GuidToString<wchar_t>(m_instanceId);
m_tempPath = tempFolder / instanceIdString;
// Make sure the directories of interest exist. Attributes are added
// separately because on upgrade directories without attributes may
// already exist.
EnsureDirectory(m_configuration.BasePath.c_str());
auto ensureDirectoryWithAttributes = [&](LPCWSTR directory, ULONG permissions) {
EnsureDirectoryWithAttributes(
(m_configuration.BasePath / directory).c_str(), permissions, LX_UID_ROOT, LX_GID_ROOT, Flags, m_configuration.Version);
};
ensureDirectoryWithAttributes(LXSS_ROOTFS_DIRECTORY, LXSS_ROOTFS_PERMISSIONS);
// If this is the legacy distribution, ensure that the additional LxFs
// directories exist and have the correct attributes. Otherwise, ensure that
// the rootfs/mnt directory exists for DrvFs mounts.
switch (m_configuration.Version)
{
case LXSS_DISTRO_VERSION_LEGACY:
WI_ASSERT(IsEqualGUID(LXSS_LEGACY_DISTRO_GUID, m_configuration.DistroId));
ensureDirectoryWithAttributes(LXSS_MNT_MOUNT_NT, LXSS_MNT_PERMISSIONS);
ensureDirectoryWithAttributes(LXSS_CACHE_MOUNT_NT, LXSS_CACHE_PERMISSIONS);
ensureDirectoryWithAttributes(LXSS_DATA_MOUNT_NT, LXSS_DATA_PERMISSIONS);
ensureDirectoryWithAttributes(LXSS_ROOT_HOME_MOUNT_NT, LXSS_ROOTFS_PERMISSIONS);
ensureDirectoryWithAttributes(LXSS_HOME_MOUNT_NT, LXSS_HOME_PERMISSIONS);
break;
case LXSS_DISTRO_VERSION_1:
case LXSS_DISTRO_VERSION_CURRENT:
break;
DEFAULT_UNREACHABLE;
}
// Wipe out and then recreate the temporary directory.
m_tempDirectory = WipeAndOpenDirectory(m_tempPath.c_str());
// Open handle to rootfs directory for the instance.
m_rootDirectory = OpenDirectoryHandle(m_configuration.BasePath.c_str(), true);
}
wil::unique_handle LxssInstance::_CreateLxProcess(
_In_ const std::shared_ptr<LxssMessagePort>& MessagePort,
_In_ const CreateLxProcessData& CreateProcessData,
_In_ const std::vector<wil::unique_handle>& StdHandles,
_In_ const wil::unique_handle& Token,
_In_ ULONG DefaultUid,
_Out_opt_ PHANDLE ServerPortHandle)
{
auto lock = MessagePort->Lock();
// Send a create process message for the session leader.
auto Message = _CreateLxProcessMarshalMessage(MessagePort, CreateProcessData, StdHandles, Token, DefaultUid);
WI_ASSERT(Message.size() <= ULONG_MAX);
const auto MessageLocal = (PLX_INIT_CREATE_PROCESS)Message.data();
const bool AllowOOBE = WI_IsFlagSet(MessageLocal->Common.Flags, LxInitCreateProcessFlagAllowOOBE);
auto HandleEraser =
wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&] { _ReleaseHandlesFromLxProcessMarshalMessage(MessagePort, MessageLocal); });
std::unique_ptr<LxssServerPort> ServerPort;
if (ARGUMENT_PRESENT(ServerPortHandle) || AllowOOBE)
{
ServerPort = std::make_unique<LxssServerPort>(MessagePort->CreateUnnamedServer(&MessageLocal->IpcServerId));
}
MessagePort->Send(Message.data(), gsl::narrow_cast<ULONG>(Message.size()));
if (AllowOOBE)
{
auto OobeMessagePort = ServerPort->WaitForConnection();
{
m_oobeCompleteEvent.create(wil::EventOptions::ManualReset);
auto impersonate = wil::CoImpersonateClient();
auto registration = wsl::windows::service::DistributionRegistration::Open(
wsl::windows::common::registry::OpenLxssUserKey().get(), m_configuration.DistroId);
// Wait for a potential previous oobe thread to complete before creating a new one.
if (m_oobeThread.joinable())
{
m_oobeThread.join();
}
m_oobeThread = std::thread([this, OobeMessagePort = std::move(OobeMessagePort), registration = std::move(registration)]() mutable {
try
{
// N.B. The LX_INIT_OOBE_RESULT message is only sent once the OOBE process completes, which might be waiting on user input.
// Do no set a timeout here otherwise the OOBE flow will fail if the OOBE process takes longer than expected.
auto Message = OobeMessagePort->Receive(INFINITE);
auto* OobeResult = gslhelpers::try_get_struct<LX_INIT_OOBE_RESULT>(gsl::make_span(Message));
THROW_HR_IF(E_INVALIDARG, !OobeResult || (OobeResult->Header.MessageType != LxInitOobeResult));
WSL_LOG(
"OOBEResult",
TraceLoggingValue(OobeResult->Result, "Result"),
TraceLoggingValue(OobeResult->DefaultUid, "DefaultUid"),
TraceLoggingValue(m_configuration.Name.c_str(), "Name"),
TraceLoggingValue(1, "Version"));
if (OobeResult->Result == 0)
{
// OOBE was successful, don't run it again.
m_configuration.RunOOBE = false;
registration.Write(wsl::windows::service::Property::RunOOBE, 0);
if (OobeResult->DefaultUid != -1)
{
registration.Write(wsl::windows::service::Property::DefaultUid, static_cast<int>(OobeResult->DefaultUid));
m_defaultUid = static_cast<int>(OobeResult->DefaultUid);
}
}
}
CATCH_LOG()
m_oobeCompleteEvent.SetEvent();
});
}
}
// Wait for the session leader to send the process identifier and unmarshal
// the process handle.
LXBUS_IPC_PROCESS_ID ProcessId = {};
MessagePort->Receive(&ProcessId, sizeof(ProcessId));
HandleEraser.release();
auto ProcessEraser = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&] {
// Reply to the init process that the process is not unmarshaled.
ProcessId = 0;
MessagePort->Send(&ProcessId, sizeof(ProcessId));
});
wil::unique_handle ProcessHandle = MessagePort->UnmarshalProcess(ProcessId);
// Reply to the init process that the process is unmarshaled.
ProcessId = 1;
MessagePort->Send(&ProcessId, sizeof(ProcessId));
ProcessEraser.release();
if (ARGUMENT_PRESENT(ServerPortHandle))
{
*ServerPortHandle = ServerPort->ReleaseServerPort();
}
return ProcessHandle;
}
std::vector<gsl::byte> LxssInstance::_CreateLxProcessMarshalMessage(
_In_ const std::shared_ptr<LxssMessagePort>& MessagePort,
_In_ const CreateLxProcessData& CreateProcessData,
_In_ const std::vector<wil::unique_handle>& StdHandles,
_In_ const wil::unique_handle& Token,
_In_ ULONG DefaultUid) const
{
// Allocate a message and initialize the common parameters.
auto Message = LxssCreateProcess::CreateMessage(LxInitMessageCreateProcess, CreateProcessData, DefaultUid);
const auto MessageLocal = (PLX_INIT_CREATE_PROCESS)Message.data();
{
static_assert(LX_INIT_CREATE_PROCESS_USE_CONSOLE == 0);
}
{
static_assert(LX_INIT_CREATE_PROCESS_USE_CONSOLE == LXSS_HANDLE_USE_CONSOLE);
}
// Marshal the standard handles.
auto Eraser =
wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&] { _ReleaseHandlesFromLxProcessMarshalMessage(MessagePort, MessageLocal); });
for (size_t Index = 0; Index < StdHandles.size(); ++Index)
{
if (StdHandles[Index])
{
LXBUS_IPC_MESSAGE_MARSHAL_HANDLE_DATA HandleData = {};
HandleData.Handle = HandleToUlong(StdHandles[Index].get());
if (Index == 0)
{
HandleData.HandleType = LxBusIpcMarshalHandleTypeInput;
}
else
{
HandleData.HandleType = LxBusIpcMarshalHandleTypeOutput;
}
MessageLocal->StdFdIds[Index] = MessagePort->MarshalHandle(&HandleData);
}
}
// Marshal the token.
{
// Acquire assign primary token in order to pass the primary token for the new process.
auto revertPriv = wsl::windows::common::security::AcquirePrivilege(SE_ASSIGNPRIMARYTOKEN_NAME);
MessageLocal->ForkTokenId = MessagePort->MarshalForkToken(Token.get());
}
if (m_configuration.RunOOBE && CreateProcessData.Filename.empty() && CreateProcessData.CommandLine.empty())
{
WI_SetFlag(MessageLocal->Common.Flags, LxInitCreateProcessFlagAllowOOBE);
}
Eraser.release();
return Message;
}
void LxssInstance::_ReleaseHandlesFromLxProcessMarshalMessage(_In_ const std::shared_ptr<LxssMessagePort>& MessagePort, _In_ PLX_INIT_CREATE_PROCESS Message)
{
if (Message->ForkTokenId != 0)
{
try
{
MessagePort->ReleaseForkToken(Message->ForkTokenId);
}
CATCH_LOG()
}
for (ULONG Index = 0; Index < RTL_NUMBER_OF(Message->StdFdIds); ++Index)
{
if (Message->StdFdIds[Index] != LXBUS_IPC_CONSOLE_ID_INVALID)
{
try
{
MessagePort->ReleaseHandle(Message->StdFdIds[Index]);
}
CATCH_LOG()
}
}
}
std::vector<unique_lxss_addmount> LxssInstance::_InitializeMounts() const
{
// Legacy distributions have more than one LxFs mount. If this is a legacy
// distribution add the /home, /root, /data, /cache, and /mnt LxFs mounts.
std::vector<unique_lxss_addmount> mounts;
auto addMount = [&](PCWSTR directory, PCWSTR source, LPCSTR target, ULONG mode) {
mounts.emplace_back(CreateMount((m_configuration.BasePath / directory).c_str(), source, target, LXSS_FS_TYPE_LXFS, mode));
};
switch (m_configuration.Version)
{
case LXSS_DISTRO_VERSION_LEGACY:
WI_ASSERT(IsEqualGUID(LXSS_LEGACY_DISTRO_GUID, m_configuration.DistroId));
addMount(LXSS_ROOT_HOME_MOUNT_NT, LXSS_ROOT_HOME_MOUNT_NT, LXSS_ROOT_HOME_MOUNT_LXSS, LXSS_ROOT_PERMISSIONS);
addMount(LXSS_HOME_MOUNT_NT, LXSS_HOME_MOUNT_NT, LXSS_HOME_MOUNT_LXSS, LXSS_HOME_PERMISSIONS);
addMount(LXSS_DATA_MOUNT_NT, LXSS_DATA_MOUNT_NT, LXSS_DATA_MOUNT_LXSS, LXSS_DATA_PERMISSIONS);
addMount(LXSS_CACHE_MOUNT_NT, LXSS_CACHE_MOUNT_NT, LXSS_CACHE_MOUNT_LXSS, LXSS_CACHE_PERMISSIONS);
addMount(LXSS_MNT_MOUNT_NT, LXSS_MNT_MOUNT_NT, LXSS_MNT_MOUNT_LXSS, LXSS_MNT_PERMISSIONS);
default:
break;
}
return mounts;
}
void LxssInstance::_StartInstance(_In_ ULONG DistributionFlags)
{
std::vector<unique_lxss_addmount> mounts;
wil::unique_handle instanceToken;
{
// Be in the right session for creating the instance parameters.
const auto userToken = wsl::windows::common::security::GetUserToken(TokenImpersonation);
auto runAsUser = wil::impersonate_token(userToken.get());
// Initialize mount points.
mounts = _InitializeMounts();
// Create token for the instance
instanceToken = wsl::windows::common::security::CreateRestrictedToken(userToken.get());
}
// Create a new instance.
LX_KINSTANCECREATESTART createParameters = {};
createParameters.RootFsType = LXSS_DISTRO_USES_WSL_FS(m_configuration.Version) ? LXSS_FS_TYPE_WSLFS : LXSS_FS_TYPE_LXFS;
WI_SetFlagIf(createParameters.Flags, LX_KINSTANCECREATESTART_FLAG_DISABLE_DRIVE_MOUNTING, WI_IsFlagClear(DistributionFlags, LXSS_DISTRO_FLAGS_ENABLE_DRIVE_MOUNTING));
createParameters.InstanceId = m_instanceId;
createParameters.RootDirectoryHandle = HandleToULong(m_rootDirectory.get());
createParameters.TempDirectoryHandle = HandleToULong(m_tempDirectory.get());
createParameters.JobHandle = HandleToULong(m_instanceJob.get());
createParameters.TokenHandle = HandleToULong(instanceToken.get());
createParameters.KernelCommandLine = LXSS_DISTRO_DEFAULT_KERNEL_COMMAND_LINE;
createParameters.NumPathsToMap = static_cast<ULONG>(mounts.size());
createParameters.PathsToMap = static_cast<PLX_KMAPPATHS_ADDMOUNT>(mounts.data());
createParameters.InstanceTerminatedEventHandle = HandleToULong(m_instanceTerminatedEvent.get());
const wil::unique_hfile nulDevice{OpenNulDevice(GENERIC_READ | GENERIC_WRITE)};
LX_KINIT_FILE_DESCRIPTOR initFileDescriptors[] = {
{nulDevice.get(), LX_O_RDONLY, 0}, {nulDevice.get(), LX_O_WRONLY, 0}, {nulDevice.get(), LX_O_WRONLY, 0}};
createParameters.NumInitFileDescriptors = RTL_NUMBER_OF(initFileDescriptors);
createParameters.InitFileDescriptors = initFileDescriptors;
{
// Acquire assign primary token in order to pass the primary token for init process.
auto revertPriv = wsl::windows::common::security::AcquirePrivilege(SE_ASSIGNPRIMARYTOKEN_NAME);
THROW_IF_NTSTATUS_FAILED(::LxssClientInstanceCreate(&createParameters, &m_instanceHandle));
}
auto deleter = wil::scope_exit([&] { LOG_IF_NTSTATUS_FAILED(::LxssClientInstanceDestroy(m_instanceHandle.get())); });
// Launch the instance.
const wil::unique_handle clientProcess = wsl::windows::common::wslutil::OpenCallingProcess(PROCESS_CREATE_PROCESS | SYNCHRONIZE);
THROW_IF_NTSTATUS_FAILED(::LxssClientInstanceStart(m_instanceHandle.get(), clientProcess.get()));
deleter.release();
}
void LxssInstance::_UpdateNetworkInformation()
try
{
// Impersonate the service.
auto runAsSelf = wil::run_as_self();
// Update the instance's DNS information.
m_dnsInfo.UpdateNetworkInformation();
// Update the resolv.conf file if it has changed.
_UpdateNetworkConfigurationFiles(false);
return;
}
CATCH_LOG()
void LxssInstance::_UpdateNetworkConfigurationFiles(_In_ bool UpdateAlways)
{
// Generate contents of /etc/resolv.conf file
wsl::core::networking::DnsSettingsFlags flags = wsl::core::networking::DnsSettingsFlags::IncludeIpv6Servers;
WI_SetFlagIf(flags, wsl::core::networking::DnsSettingsFlags::IncludeVpn, m_enableVpnDetection);
const auto dnsSettings = m_dnsInfo.GetDnsSettings(flags);
std::string fileContents = GenerateResolvConf(dnsSettings);
std::lock_guard<std::mutex> lock(m_resolvConfLock);
if (!UpdateAlways && (fileContents == m_lastResolvConfContents))
{
return;
}
// Construct the network information message.
wsl::shared::MessageWriter<LX_INIT_NETWORK_INFORMATION> message(LxInitMessageNetworkInformation);
message.WriteString(message->FileHeaderIndex, wsl::shared::string::WideToMultiByte(LX_INIT_RESOLVCONF_FULL_HEADER));
message.WriteString(message->FileContentsIndex, fileContents);
auto messageSpan = message.Span();
// Send the message.
auto messagePortLock = m_InitMessagePort->Lock();
m_InitMessagePort->Send(messageSpan.data(), gsl::narrow_cast<ULONG>(messageSpan.size()));
m_lastResolvConfContents = std::move(fileContents);
}
void LxssInstance::_InitializeNetworking()
{
m_ipTables.EnableIpTablesSupport(m_instanceHandle);
// Register for network connectivity change notifications to update network information.
LOG_IF_WIN32_ERROR(::NotifyNetworkConnectivityHintChange(
[](PVOID context, NL_NETWORK_CONNECTIVITY_HINT) { static_cast<LxssInstance*>(context)->_UpdateNetworkInformation(); }, this, TRUE, &m_networkNotificationHandle));
}
void LxssInstance::_InitiateConnectionToInitProcess()
{
// Register the server, wait for the init process to connect to the server,
// and create the message event.
m_ServerPort->RegisterLxBusServer(m_instanceHandle, LX_INIT_SERVER_NAME);
std::unique_ptr<LxssMessagePort> NewMessagePort = m_ServerPort->WaitForConnection(LXSS_INIT_CONNECTION_TIMEOUT_MS);
// Associate the server port with the new message port and create a console
// manager that will be used to manage session leaders.
NewMessagePort->SetServerPort(m_ServerPort);
m_InitMessagePort = std::make_shared<LxssMessagePort>(std::move(NewMessagePort));
m_consoleManager = ConsoleManager::CreateConsoleManager(m_InitMessagePort);
}
void LxssInstance::_InitializeConfiguration(_In_ const std::filesystem::path& Plan9SocketPath)
{
// If DrvFs mounting is supported, initialize a bitmap of all fixed drives.
ULONG fixedDrives = 0;
if (WI_IsFlagSet(m_configuration.Flags, LXSS_DISTRO_FLAGS_ENABLE_DRIVE_MOUNTING))
{
fixedDrives = EnumerateFixedDrives().first;
}
const auto timezone = wsl::windows::common::helpers::GetLinuxTimezone(m_userToken.get());
ULONG featureFlags{};
WI_SetFlagIf(featureFlags, LxInitFeatureRootfsCompressed, WI_IsFlagSet(GetFileAttributesW(m_configuration.BasePath.c_str()), FILE_ATTRIBUTE_COMPRESSED));
auto message = wsl::windows::common::helpers::GenerateConfigurationMessage(
m_configuration.Name, fixedDrives, m_defaultUid, timezone, Plan9SocketPath.wstring(), featureFlags);
// Send the message to the init daemon.
auto lock = m_InitMessagePort->Lock();
m_InitMessagePort->Send(message.data(), static_cast<ULONG>(message.size()));
// Init replies with information about the distribution.
auto buffer = m_InitMessagePort->Receive();
const auto span = gsl::make_span(buffer);
const auto* response = gslhelpers::try_get_struct<LX_INIT_CONFIGURATION_INFORMATION_RESPONSE>(span);
THROW_HR_IF(E_UNEXPECTED, !response || response->Header.MessageType != LxInitMessageInitializeResponse);
m_defaultUid = response->DefaultUid;
if (response->VersionIndex > 0)
{
m_configuration.OsVersion = wsl::shared::string::MultiByteToWide(wsl::shared::string::FromSpan(span, response->VersionIndex));
m_distributionInfo.Version = m_configuration.OsVersion.c_str();
}
if (response->FlavorIndex > 0)
{
m_configuration.Flavor = wsl::shared::string::MultiByteToWide(wsl::shared::string::FromSpan(span, response->FlavorIndex));
m_distributionInfo.Flavor = m_configuration.Flavor.c_str();
}
}