reactos/sdk/lib/smlib/smutils.c
Hermès Bélusca-Maïto 0e14378d3e
[SMDLL][SMLIB] Deprecate the legacy ROS-specific SMDLL and improve SM client functions. (#4821)
This DLL was exporting legacy NT-incompatible or ROS-specific SM client
functions, that have been since 10 years now (2012) replaced by the new
NT-compatible SM:

- SmConnectApiPort(): was just SmConnectToSm().

- SmCompleteSession():
  The legacy SMSS used it for when a subsystem initialization was finished.
  Now (NT-compatible) this function is called by subsystems **only** when a
  subsystem session **terminates**: SmSessionComplete().

- SmExecuteProgram(): was just the client side of SmLoadDeferedSubSystem()
  (whose server side is not implemented yet). The legacy SM "old" SmExecPgm
  implementation actually was "SmLoadDeferedSubSystem"...

- SmLookupSubsystem(): is a utility-only function to read any registry value
  inside "Session Manager\SubSystems".

Move SMDLL's readme into SMLIB and update its contents.

Collect some residual useful functions into smutils.c (and moved in SMLIB,
though not compiled yet):
- SmExecuteProgram(), now implemented as a wrapper around SmExecPgm();
- SmLookupSubsystem(), described above;
- SmQueryInformation(), that retrieves a list of currently-running subsystems.

[SMLIB] Validate SbApiPortName's length in SmConnectToSm().
Fix CommandLine length validation in SmStartCsr().

Add documentation (+ SAL annotations) to the NT-compatible SMSS client functions.

smmsg.h: Add both Win32 and Win64 struct sizes C_ASSERTs for those whose size
change between these two processor architecture sizes.

[SMLIB] Introduce SmSendMsgToSm() as helper to send data into the SM LPC port.
+ Make the other API functions use it.

It should be observed that in Vista+, both functions SmConnectToSm() and this
new SmSendMsgToSm() are exported by NTDLL under the names RtlConnectToSm()
and RtlSendMsgToSm() (and use the same signature).
See: https://www.geoffchappell.com/studies/windows/win32/ntdll/history/names60.htm

[NTDLL] Correctly stub RtlConnectToSm() and RtlSendMsgToSm().
[NTDLL_VISTA] Link to SMLIB and simply export RtlConnectToSm() and RtlSendMsgToSm().
2022-11-08 17:40:53 +01:00

453 lines
14 KiB
C

/*
* PROJECT: ReactOS SM Helper Library
* LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later)
* PURPOSE: Utility functions built around the client SM API
* COPYRIGHT: Copyright 2005 Emanuele Aliberti <ea@reactos.com>
* Copyright 2022 Hermès Bélusca-Maïto <hermes.belusca-maito@reactos.org>
*/
#include "precomp.h"
#define WIN32_NO_STATUS
#define _INC_WINDOWS
#define COM_NO_WINDOWS_H
#include <windef.h>
#include <winreg.h>
#define NTOS_MODE_USER
#include <ndk/cmfuncs.h>
#include <sm/helper.h>
#define NDEBUG
#include <debug.h>
#if DBG
BOOLEAN SmpDebug = TRUE;
#else
BOOLEAN SmpDebug = FALSE;
#endif
/**
* @brief
* This function is used to make the SM start an external process
* under an already-loaded environment subsystem server.
*
* @param[in] SmApiPort
* Port handle returned by SmConnectToSm().
*
* @param[in] Program
* Fully qualified NT name of the executable to load.
*
* @return
* Success status as handed by the SM reply; otherwise a failure
* status code.
*
* @remark
* Adapted from SMSS' SmpExecuteInitialCommand() and SmpExecuteImage().
**/
NTSTATUS
NTAPI
SmExecuteProgram(
_In_ HANDLE SmApiPort,
_In_ PUNICODE_STRING Program /*,
_Out_opt_ PRTL_USER_PROCESS_INFORMATION ProcessInformation*/)
{
// ULONG MuSessionId;
NTSTATUS Status;
RTL_USER_PROCESS_INFORMATION ProcessInfo;
PRTL_USER_PROCESS_PARAMETERS ProcessParameters;
PUNICODE_STRING FileName = Program; // FIXME!
PUNICODE_STRING Directory = NULL; // FIXME!
PUNICODE_STRING CommandLine = NULL; // FIXME!
PVOID SmpDefaultEnvironment = NtCurrentPeb()->ProcessParameters->Environment;
// UNICODE_STRING SmpDefaultLibPath;
DPRINT("SMLIB: %s(%p, '%wZ') called\n",
__FUNCTION__, SmApiPort, Program);
/* Parameters validation */
if (!SmApiPort)
return STATUS_INVALID_PARAMETER_1;
if (!Program)
return STATUS_INVALID_PARAMETER_2;
/* Create parameters for the target process, using the current environment */
Status = RtlCreateProcessParameters(&ProcessParameters,
FileName,
/* SmpDefaultLibPath.Length ?
&SmpDefaultLibPath : */ NULL,
Directory,
CommandLine,
SmpDefaultEnvironment,
NULL,
NULL,
NULL,
0);
if (!NT_SUCCESS(Status))
{
DPRINT1("SMLIB: RtlCreateProcessParameters failed for %wZ - Status == %lx\n",
FileName, Status);
return Status;
}
/* Set the size field as required */
ProcessInfo.Size = sizeof(ProcessInfo);
/* Otherwise inherit the flag that was passed to SMSS itself */
ProcessParameters->DebugFlags = SmpDebug;
/* And always force NX for anything that SMSS launches */
ProcessParameters->Flags |= RTL_USER_PROCESS_PARAMETERS_NX;
/* Now create the process in suspended state */
Status = RtlCreateUserProcess(FileName,
OBJ_CASE_INSENSITIVE,
ProcessParameters,
NULL,
NULL,
NULL,
FALSE,
NULL,
NULL,
&ProcessInfo);
RtlDestroyProcessParameters(ProcessParameters);
if (!NT_SUCCESS(Status))
{
/* If we couldn't create it, fail back to the caller */
DPRINT1("SMLIB: Failed load of %wZ - Status == %lx\n",
FileName, Status);
return Status;
}
// /* Now duplicate the handle to this process */
// Status = NtDuplicateObject(NtCurrentProcess(),
// ProcessInfo.ProcessHandle,
// NtCurrentProcess(),
// InitialCommandProcess,
// PROCESS_ALL_ACCESS,
// 0,
// 0);
// if (!NT_SUCCESS(Status))
// {
// /* Kill it utterly if duplication failed */
// DPRINT1("SMLIB: DupObject Failed. Status == %lx\n", Status);
// NtTerminateProcess(ProcessInfo.ProcessHandle, Status);
// NtResumeThread(ProcessInfo.ThreadHandle, NULL);
// NtClose(ProcessInfo.ThreadHandle);
// NtClose(ProcessInfo.ProcessHandle);
// return Status;
// }
/* Call SM and wait for a reply */
Status = SmExecPgm(SmApiPort, &ProcessInfo, TRUE);
NtClose(ProcessInfo.ThreadHandle);
NtClose(ProcessInfo.ProcessHandle);
// if (ProcessInformation)
// *ProcessInformation = ProcessInfo;
DPRINT("SMLIB: %s returned (Status=0x%08lx)\n", __FUNCTION__, Status);
return Status;
}
/**
* @brief
* Reads from the registry key
* \Registry\SYSTEM\CurrentControlSet\Control\Session Manager\SubSystems
* the value specified by Name.
*
* @param[in] Name
* Name of the program to run, that is a value's name in
* the SM registry key "SubSystems".
*
* @param[out] Data
* What the registry gave back for Name.
*
* @param[in,out] DataLength
* How much Data the registry returns.
*
* @param[out] DataType
* Optional pointer to a variable that receives the type of data
* stored in the specified value.
*
* @param[in] Environment
* Optional environment to be used to possibly expand Data before
* returning it back; if set to NULL, no expansion will be performed.
*
* @return
* Success status if the specified subsystem existed and its information
* has been retrieved successfully; otherwise a failure status code.
**/
NTSTATUS
NTAPI
SmLookupSubsystem(
_In_ PWSTR Name,
_Out_ PWSTR Data,
_Inout_ PULONG DataLength,
_Out_opt_ PULONG DataType,
_In_opt_ PVOID Environment)
{
NTSTATUS Status;
UNICODE_STRING usKeyName;
OBJECT_ATTRIBUTES Oa;
HANDLE hKey = NULL;
UNICODE_STRING usValueName;
PWCHAR KeyValueInformation = NULL;
ULONG KeyValueInformationLength = 1024;
ULONG ResultLength = 0;
PKEY_VALUE_PARTIAL_INFORMATION kvpi;
DPRINT("SMLIB: %s(Name='%S') called\n", __FUNCTION__, Name);
/*
* Prepare the key name to scan and
* related object attributes.
*/
RtlInitUnicodeString(&usKeyName,
L"\\Registry\\Machine\\SYSTEM\\CurrentControlSet\\Control\\Session Manager\\SubSystems");
InitializeObjectAttributes(&Oa,
&usKeyName,
OBJ_CASE_INSENSITIVE,
NULL,
NULL);
/*
* Open the key. This MUST NOT fail, if the
* request is for a legitimate subsystem.
*/
Status = NtOpenKey(&hKey,
MAXIMUM_ALLOWED,
&Oa);
if (!NT_SUCCESS(Status))
{
DPRINT1("%s: NtOpenKey failed (Status=0x%08lx)\n", __FUNCTION__, Status);
return Status;
}
KeyValueInformation = RtlAllocateHeap(RtlGetProcessHeap(),
0,
KeyValueInformationLength);
if (!KeyValueInformation)
{
NtClose(hKey);
return STATUS_NO_MEMORY;
}
kvpi = (PKEY_VALUE_PARTIAL_INFORMATION)KeyValueInformation;
RtlInitUnicodeString(&usValueName, Name);
Status = NtQueryValueKey(hKey,
&usValueName,
KeyValuePartialInformation,
KeyValueInformation,
KeyValueInformationLength,
&ResultLength);
if (!NT_SUCCESS(Status))
{
DPRINT1("%s: NtQueryValueKey failed (Status=0x%08lx)\n", __FUNCTION__, Status);
RtlFreeHeap(RtlGetProcessHeap(), 0, KeyValueInformation);
NtClose(hKey);
return Status;
}
DPRINT("kvpi.TitleIndex = %lu\n", kvpi->TitleIndex);
DPRINT("kvpi.Type = %lu\n", kvpi->Type);
DPRINT("kvpi.DataLength = %lu\n", kvpi->DataLength);
if (Data && DataLength)
{
if (DataType)
*DataType = kvpi->Type;
if (Environment && (kvpi->Type == REG_EXPAND_SZ))
{
UNICODE_STRING Source;
UNICODE_STRING Destination;
PWCHAR DestinationBuffer = NULL;
ULONG Length;
DPRINT("SMLIB: %s: value will be expanded\n", __FUNCTION__);
Length = 2 * KeyValueInformationLength;
DestinationBuffer = RtlAllocateHeap(RtlGetProcessHeap(),
0,
Length);
if (!DestinationBuffer)
{
Status = STATUS_NO_MEMORY;
}
else
{
Source.Length = (USHORT)kvpi->DataLength;
Source.MaximumLength = Source.Length;
Source.Buffer = (PWCHAR)&kvpi->Data;
RtlInitEmptyUnicodeString(&Destination,
DestinationBuffer,
(USHORT)Length);
Length = 0;
Status = RtlExpandEnvironmentStrings_U(Environment,
&Source,
&Destination,
&Length);
if (NT_SUCCESS(Status))
{
*DataLength = min(*DataLength, Destination.Length);
RtlCopyMemory(Data, Destination.Buffer, *DataLength);
}
RtlFreeHeap(RtlGetProcessHeap(), 0, DestinationBuffer);
}
}
else
{
DPRINT("SMLIB: %s: value won't be expanded\n", __FUNCTION__);
*DataLength = min(*DataLength, kvpi->DataLength);
RtlCopyMemory(Data, &kvpi->Data, *DataLength);
}
}
else
{
DPRINT1("SMLIB: %s: Data or DataLength is NULL!\n", __FUNCTION__);
Status = STATUS_INVALID_PARAMETER;
}
RtlFreeHeap(RtlGetProcessHeap(), 0, KeyValueInformation);
NtClose(hKey);
return Status;
}
/**
* @brief
* Retrieves information about subsystems registered with the SM.
*
* @param[in] SmApiPort
* Port handle returned by SmConnectToSm().
*
* @param[in] SmInformationClass
* An SM information class ID:
* - SmBasicInformation:
* The number and list of registered subsystems. Data is returned
* in a SM_BASIC_INFORMATION structure.
* - SmSubSystemInformation:
* Information about a particular registered subsystem. Data is
* returned in a SM_SUBSYSTEM_INFORMATION structure.
*
* @param[in,out] Data
* Pointer to storage for the information to request. Either a
* SM_BASIC_INFORMATION or a SM_SUBSYSTEM_INFORMATION, depending
* on the information class.
*
* @param[in] DataLength
* Length in bytes of the Data buffer; it must be set and must
* match the SmInformationClass information size.
*
* @param[in,out] ReturnedDataLength
* Optional pointer to storage to receive the size of the returned data.
*
* @return
* STATUS_SUCCESS when the information asked for has been retrieved.
* STATUS_INVALID_PARAMETER_2 if an invalid information class has been provided.
* STATUS_INFO_LENGTH_MISMATCH in case either DataLength was set to 0
* or to a value that does not match what the SmInformationClass requires.
* Otherwise, a success status as handed by the SM reply, or a failure
* status code.
*
* @remark
* This API is ReactOS-specific and not in NT.
*/
NTSTATUS
NTAPI
SmQueryInformation(
_In_ HANDLE SmApiPort,
_In_ SM_INFORMATION_CLASS SmInformationClass,
_Inout_ PVOID Data,
_In_ ULONG DataLength,
_Inout_opt_ PULONG ReturnedDataLength)
{
#if defined(__REACTOS__) && DBG
NTSTATUS Status;
SM_API_MSG SmApiMsg = {0};
PSM_QUERYINFO_MSG QueryInfo = &SmApiMsg.u.QueryInfo;
/* Marshal data in the port message */
switch (SmInformationClass)
{
case SmBasicInformation:
if (DataLength != sizeof(SM_BASIC_INFORMATION))
{
return STATUS_INFO_LENGTH_MISMATCH;
}
QueryInfo->SmInformationClass = SmBasicInformation;
QueryInfo->DataLength = DataLength;
QueryInfo->BasicInformation.SubSystemCount = 0;
break;
case SmSubSystemInformation:
if (DataLength != sizeof(SM_SUBSYSTEM_INFORMATION))
{
return STATUS_INFO_LENGTH_MISMATCH;
}
QueryInfo->SmInformationClass = SmSubSystemInformation;
QueryInfo->DataLength = DataLength;
QueryInfo->SubSystemInformation.SubSystemId =
((PSM_SUBSYSTEM_INFORMATION)Data)->SubSystemId;
break;
default:
return STATUS_INVALID_PARAMETER_2;
}
/* SM API to invoke */
SmApiMsg.ApiNumber = SM_API_QUERY_INFORMATION;
/* NOTE: Repurpose the DataLength variable */
DataLength = sizeof(SM_QUERYINFO_MSG);
/* Fill out the Port Message Header */
// SmApiMsg.h.u2.s2.Type = LPC_NEW_MESSAGE;
SmApiMsg.h.u2.ZeroInit = 0;
/* DataLength = user_data_size + anything between
* header and data, including intermediate padding */
SmApiMsg.h.u1.s1.DataLength = (CSHORT)DataLength +
FIELD_OFFSET(SM_API_MSG, u) - sizeof(SmApiMsg.h);
/* TotalLength = sizeof(SmApiMsg) on <= NT5.2, otherwise:
* DataLength + header_size == user_data_size + FIELD_OFFSET(SM_API_MSG, u)
* without structure trailing padding */
SmApiMsg.h.u1.s1.TotalLength = SmApiMsg.h.u1.s1.DataLength + sizeof(SmApiMsg.h);
/* Send the LPC message and wait for a reply */
Status = NtRequestWaitReplyPort(SmApiPort, &SmApiMsg.h, &SmApiMsg.h);
if (!NT_SUCCESS(Status))
{
DPRINT1("SMLIB: %s: NtRequestWaitReplyPort failed, Status: 0x%08lx\n",
__FUNCTION__, Status);
return Status;
}
/* Unmarshal data */
RtlCopyMemory(Data,
&QueryInfo->BasicInformation,
QueryInfo->DataLength);
/* Use caller provided storage to store data size */
if (ReturnedDataLength)
*ReturnedDataLength = QueryInfo->DataLength;
/* Return the real status */
return SmApiMsg.ReturnValue;
#else
return STATUS_NOT_IMPLEMENTED;
#endif /* defined(__REACTOS__) && DBG */
}
/* EOF */