Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

879 lines
24 KiB

/*++
Copyright (c) Microsoft Corporation. All rights reserved.
Module Name:
pnpsec.c
Abstract:
This module implements the security checks required to access the Plug and
Play manager APIs.
VerifyClientPrivilege
VerifyClientAccess
VerifyKernelInitiatedEjectPermissions
Author:
James G. Cavalaris (jamesca) 05-Apr-2002
Environment:
User-mode only.
Revision History:
05-Apr-2002 Jim Cavalaris (jamesca)
Creation and initial implementation.
--*/
//
// includes
//
#include "precomp.h"
#pragma hdrstop
#include "umpnpi.h"
#include "umpnpdat.h"
#include <svcsp.h>
#pragma warning(disable:4204)
#pragma warning(disable:4221)
//
// Global data provided by the service controller.
// Specifies well-known account and group SIDs for us to use.
//
extern PSVCS_GLOBAL_DATA PnPGlobalData;
//
// Security descriptor of the Plug and Play Manager security object, used to
// control access to the Plug and Play Manager APIs.
//
PSECURITY_DESCRIPTOR PlugPlaySecurityObject = NULL;
//
// Generic security mapping for the Plug and Play Manager security object.
//
GENERIC_MAPPING PlugPlaySecurityObjectMapping = PLUGPLAY_GENERIC_MAPPING;
//
// Function prototypes.
//
BOOL
VerifyTokenPrivilege(
IN HANDLE hToken,
IN ULONG Privilege,
IN LPCWSTR ServiceName
);
//
// Access and privilege check routines.
//
BOOL
VerifyClientPrivilege(
IN handle_t hBinding,
IN ULONG Privilege,
IN LPCWSTR ServiceName
)
/*++
Routine Description:
This routine impersonates the client associated with hBinding and checks
if the client possesses the specified privilege.
Arguments:
hBinding RPC Binding handle
Privilege Specifies the privilege to be checked.
Return value:
The return value is TRUE if the client possesses the privilege, FALSE if not
or if an error occurs.
--*/
{
RPC_STATUS rpcStatus;
BOOL bResult;
HANDLE hToken;
//
// If the specified RPC binding handle is NULL, this is an internal call so
// we assume that the privilege has already been checked.
//
if (hBinding == NULL) {
return TRUE;
}
//
// Impersonate the client to retrieve the impersonation token.
//
rpcStatus = RpcImpersonateClient(hBinding);
if (rpcStatus != RPC_S_OK) {
//
// Since we can't impersonate the client we better not do the security
// checks as ourself (they would always succeed).
//
return FALSE;
}
if (OpenThreadToken(GetCurrentThread(), TOKEN_QUERY, FALSE, &hToken)) {
bResult =
VerifyTokenPrivilege(
hToken, Privilege, ServiceName);
CloseHandle(hToken);
} else {
KdPrintEx((DPFLTR_PNPMGR_ID,
DBGF_ERRORS,
"UMPNPMGR: OpenThreadToken failed, error = %d\n",
GetLastError()));
bResult = FALSE;
}
rpcStatus = RpcRevertToSelf();
if (rpcStatus != RPC_S_OK) {
KdPrintEx((DPFLTR_PNPMGR_ID,
DBGF_ERRORS,
"UMPNPMGR: RpcRevertToSelf failed, error = %d\n",
rpcStatus));
ASSERT(rpcStatus == RPC_S_OK);
}
return bResult;
} // VerifyClientPrivilege
BOOL
VerifyTokenPrivilege(
IN HANDLE hToken,
IN ULONG Privilege,
IN LPCWSTR ServiceName
)
/*++
Routine Description:
This routine checks if the specified token possesses the specified
privilege.
Arguments:
hToken Specifies a handle to the token whose privileges are to be
checked
Privilege Specifies the privilege to be checked.
ServiceName Specifies the privileged subsystem service (operation
requiring the privilege).
Return value:
The return value is TRUE if the client possesses the privilege, FALSE if not
or if an error occurs.
--*/
{
PRIVILEGE_SET privilegeSet;
BOOL bResult = FALSE;
//
// Specify the privilege to be checked.
//
ZeroMemory(&privilegeSet, sizeof(PRIVILEGE_SET));
privilegeSet.PrivilegeCount = 1;
privilegeSet.Control = 0;
privilegeSet.Privilege[0].Luid = RtlConvertUlongToLuid(Privilege);
privilegeSet.Privilege[0].Attributes = 0;
//
// Perform the actual privilege check.
//
if (!PrivilegeCheck(hToken, &privilegeSet, &bResult)) {
KdPrintEx((DPFLTR_PNPMGR_ID,
DBGF_ERRORS,
"UMPNPMGR: PrivilegeCheck failed, error = %d\n",
GetLastError()));
bResult = FALSE;
}
//
// Generate an audit of the attempted privilege use, using the result of the
// previous check.
//
if (!PrivilegedServiceAuditAlarm(
PLUGPLAY_SUBSYSTEM_NAME,
ServiceName,
hToken,
&privilegeSet,
bResult)) {
KdPrintEx((DPFLTR_PNPMGR_ID,
DBGF_ERRORS,
"UMPNPMGR: PrivilegedServiceAuditAlarm failed, error = %d\n",
GetLastError()));
}
return bResult;
} // VerifyTokenPrivilege
BOOL
VerifyKernelInitiatedEjectPermissions(
IN HANDLE UserToken OPTIONAL,
IN BOOL DockDevice
)
/*++
Routine Description:
Checks that the user has eject permissions for the specified type of
hardware.
Arguments:
UserToken - Token of the logged in console user, NULL if no console user
is logged in.
DockDevice - TRUE if a dock is being ejected, FALSE if an ordinary device
was specified.
Return Value:
TRUE if the eject should procceed, FALSE otherwise.
--*/
{
LONG Result = ERROR_SUCCESS;
BOOL AllowUndock;
WCHAR RegStr[MAX_CM_PATH];
HKEY hKey = NULL;
DWORD dwSize, dwValue, dwType;
TOKEN_PRIVILEGES NewPrivs, OldPrivs;
//
// Only enforce eject permissions for dock devices. We do not specify per
// device ejection security for other types of devices, since most devices
// are in no way secure from removal.
//
if (!DockDevice) {
return TRUE;
}
//
// Unless the policy says otherwise, we do NOT allow undock without
// privilege check.
//
AllowUndock = FALSE;
//
// First, check the "Allow undock without having to log on" policy. If the
// policy does NOT allow undock without logon, we require that there is an
// interactive user logged on to the physical Console session, and that user
// has the SE_UNDOCK_PRIVILEGE.
//
//
// Open the System policies key.
//
if (SUCCEEDED(
StringCchPrintf(
RegStr,
SIZECHARS(RegStr),
L"%s\\%s",
pszRegPathPolicies,
pszRegKeySystem))) {
if (RegOpenKeyEx(
HKEY_LOCAL_MACHINE,
RegStr,
0,
KEY_READ,
&hKey) == ERROR_SUCCESS) {
//
// Retrieve the "UndockWithoutLogon" value.
//
dwType = 0;
dwValue = 0;
dwSize = sizeof(dwValue);
Result =
RegQueryValueEx(
hKey,
pszRegValueUndockWithoutLogon,
NULL,
&dwType,
(LPBYTE)&dwValue,
&dwSize);
if ((Result == ERROR_SUCCESS) && (dwType == REG_DWORD)) {
//
// If the value exists and is non-zero, we allow undock without
// privilege check. If the value id zero, the policy requires
// us to check the privileges of the supplied user token.
//
AllowUndock = (dwValue != 0);
} else if (Result == ERROR_FILE_NOT_FOUND) {
//
// No value means allow any undock.
//
AllowUndock = TRUE;
} else {
//
// For all remaining cases, the policy check either failed, or
// an error was encountered reading the policy. We have
// insufficient information to determine whether the eject
// should be allowed based on policy alone, so we defer any
// decision and check the privileges of the supplied user token.
//
AllowUndock = FALSE;
}
//
// Close the policy key.
//
RegCloseKey(hKey);
}
}
//
// If the policy allowed undock without logon, there is no need to check
// token privileges.
//
if (AllowUndock) {
return TRUE;
}
//
// If the policy requires privileges to be checked, but no user token was
// supplied, deny the request.
//
if (UserToken == NULL) {
return FALSE;
}
//
// Enable the required SE_UNDOCK_PRIVILEGE token privilege.
// The TOKEN_PRIVILEGES structure contains 1 LUID_AND_ATTRIBUTES, which is
// all we need for now.
//
ZeroMemory(&NewPrivs, sizeof(TOKEN_PRIVILEGES));
ZeroMemory(&OldPrivs, sizeof(TOKEN_PRIVILEGES));
NewPrivs.PrivilegeCount = 1;
NewPrivs.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
NewPrivs.Privileges[0].Luid = RtlConvertUlongToLuid(SE_UNDOCK_PRIVILEGE);
dwSize = sizeof(TOKEN_PRIVILEGES);
if (!AdjustTokenPrivileges(
UserToken,
FALSE,
&NewPrivs,
sizeof(TOKEN_PRIVILEGES),
&OldPrivs,
&dwSize)) {
return FALSE;
}
//
// Check if the required SE_UNDOCK_PRIVILEGE privilege is enabled. Note
// that this routine also audits the use of this privilege.
//
AllowUndock =
VerifyTokenPrivilege(
UserToken,
SE_UNDOCK_PRIVILEGE,
L"UNDOCK: EJECT DOCK DEVICE");
//
// Adjust the privilege back to its previous state.
//
AdjustTokenPrivileges(
UserToken,
FALSE,
&OldPrivs,
sizeof(TOKEN_PRIVILEGES),
(PTOKEN_PRIVILEGES)NULL,
(PDWORD)NULL);
return AllowUndock;
} // VerifyKernelInitiatedEjectPermissions
BOOL
CreatePlugPlaySecurityObject(
VOID
)
/*++
Routine Description:
This function creates the self-relative security descriptor which
represents the Plug and Play security object.
Arguments:
None.
Return Value:
TRUE if the object was successfully created, FALSE otherwise.
--*/
{
BOOL Status = TRUE;
NTSTATUS NtStatus;
BOOLEAN WasEnabled;
PSECURITY_DESCRIPTOR AbsoluteSd = NULL;
HANDLE TokenHandle = NULL;
//
// This routine is called from our service start routine, so we must have
// been provided with the global data block by now.
//
ASSERT(PnPGlobalData != NULL);
//
// SE_AUDIT_PRIVILEGE privilege is required to perform object access and
// privilege auditing, and must be enabled in the process token. Leave it
// enabled since we will be auditing frequently. Note that we share this
// process (services.exe) with the SCM, which also performs auditing, and
// most likely has already enabled this privilege for the process.
//
RtlAdjustPrivilege(SE_AUDIT_PRIVILEGE,
TRUE,
FALSE,
&WasEnabled);
//
// SE_SECURITY_PRIVILEGE privilege is required to create a security
// descriptor with a SACL. Impersonate ourselves to safely enable the
// privilege on our thread only.
//
NtStatus =
RtlImpersonateSelf(
SecurityImpersonation);
ASSERT(NT_SUCCESS(NtStatus));
if (!NT_SUCCESS(NtStatus)) {
return FALSE;
}
NtStatus =
RtlAdjustPrivilege(
SE_SECURITY_PRIVILEGE,
TRUE,
TRUE,
&WasEnabled);
ASSERT(NT_SUCCESS(NtStatus));
if (NT_SUCCESS(NtStatus)) {
//
// Specify the ACEs for the security object.
// Order matters! These ACEs are inserted into the DACL in the
// following order. Security access is granted or denied based on
// the order of the ACEs in the DACL.
//
// ISSUE-2002/06/25-jamesca: Description of PlugPlaySecurityObject ACEs.
// For consistent read/write access between the server-side PlugPlay
// APIs and direct registry access by the client, we apply a DACL on
// the PlugPlaySecurityObject that similar to what is applied by
// default to the client accesible parts of the Plug and Play registry
// -- specifically, SYSTEM\CCS\Control\Class. Note however that
// "Power Users" are NOT special-cased here as they are in the
// registry ACLs; they must be authenticated as a group listed below
// for any access to be granted. If the access requirements for Plug
// and Play objects such as the registry ever change, you must
// re-evaluate the ACEs below to make sure they are still
// appropriate!!
//
RTL_ACE_DATA PlugPlayAceData[] = {
//
// Local System Account is granted all access.
//
{ ACCESS_ALLOWED_ACE_TYPE,
0,
0,
GENERIC_ALL,
&(PnPGlobalData->LocalSystemSid) },
//
// Local Administrators Group is granted all access.
//
{ ACCESS_ALLOWED_ACE_TYPE,
0,
0,
GENERIC_ALL,
&(PnPGlobalData->AliasAdminsSid) },
//
// Network Group is denied any access.
// (unless granted by Local Administrators ACE, above)
//
{ ACCESS_DENIED_ACE_TYPE,
0,
0,
GENERIC_ALL,
&(PnPGlobalData->NetworkSid) },
//
// Users are granted read and execute access.
// (unless denied by Network ACE, above)
//
{ ACCESS_ALLOWED_ACE_TYPE,
0,
0,
GENERIC_READ | GENERIC_EXECUTE,
&(PnPGlobalData->AliasUsersSid) },
//
// Any access request not explicitly granted above is denied.
//
//
// Audit object-specific write access requests for everyone, for
// failure or success. Audit all access request failures.
//
// We don't audit successful read access requests, because they
// occur too frequently to be useful. We don't audit successful
// execute requests because they result in privilege checks, which
// are audited separately.
//
// Also note that we only audit the PLUGPLAY_WRITE object-specific
// right. Since the GENERIC_WRITE mapping shares standard rights
// with GENERIC_READ and GENERIC_EXECUTE, auditing successful access
// grants for any of the GENERIC_WRITE bits would also result in
// auditing any sucessful request for GENERIC_READ (and/or
// GENERIC_EXECUTE access) - which as mentioned, are too frequent.
//
{ SYSTEM_AUDIT_ACE_TYPE,
0,
FAILED_ACCESS_ACE_FLAG | SUCCESSFUL_ACCESS_ACE_FLAG,
PLUGPLAY_WRITE,
&(PnPGlobalData->WorldSid) },
//
// Audit all access failures for everyone.
//
// ISSUE-2002/06/25-jamesca: Everyone vs. Anonymous group SIDs:
// Note that for the purposes of auditing, the Everyone SID also
// includes Anonymous, though in all other cases the two groups
// are now disjoint (Windows XP and later). The named pipe used
// for our RPC endpoint only grants access to everyone however,
// so technically we will never receive RPC calls from an
// Anonymous caller.
//
{ SYSTEM_AUDIT_ACE_TYPE,
0,
FAILED_ACCESS_ACE_FLAG,
GENERIC_ALL,
&(PnPGlobalData->WorldSid) },
};
//
// Create a new Absolute Security Descriptor, specifying the LocalSystem
// account SID for both Owner and Group.
//
NtStatus =
RtlCreateAndSetSD(
PlugPlayAceData,
RTL_NUMBER_OF(PlugPlayAceData),
PnPGlobalData->LocalSystemSid,
PnPGlobalData->LocalSystemSid,
&AbsoluteSd);
ASSERT(NT_SUCCESS(NtStatus));
if (NT_SUCCESS(NtStatus)) {
NtStatus =
NtOpenThreadToken(
NtCurrentThread(),
TOKEN_QUERY,
FALSE,
&TokenHandle);
ASSERT(NT_SUCCESS(NtStatus));
if (NT_SUCCESS(NtStatus)) {
//
// Create the security object (a user-mode object is really a pseudo-
// object represented by a security descriptor that have relative
// pointers to SIDs and ACLs). This routine allocates the memory to
// hold the relative security descriptor so the memory allocated for the
// DACL, ACEs, and the absolute descriptor can be freed.
//
NtStatus =
RtlNewSecurityObject(
NULL,
AbsoluteSd,
&PlugPlaySecurityObject,
FALSE,
TokenHandle,
&PlugPlaySecurityObjectMapping);
ASSERT(NT_SUCCESS(NtStatus));
NtClose(TokenHandle);
}
//
// Free the Absolute Security Descriptor allocated by
// RtlCreateAndSetSD in the process heap. If sucessful, we should
// have a self-relative one in PlugPlaySecurityObject.
//
RtlFreeHeap(RtlProcessHeap(), 0, AbsoluteSd);
}
}
ASSERT(IsValidSecurityDescriptor(PlugPlaySecurityObject));
//
// If not successful, we could not create the security object.
//
if (!NT_SUCCESS(NtStatus)) {
ASSERT(PlugPlaySecurityObject == NULL);
PlugPlaySecurityObject = NULL;
Status = FALSE;
}
//
// Stop impersonating.
//
TokenHandle = NULL;
NtStatus =
NtSetInformationThread(
NtCurrentThread(),
ThreadImpersonationToken,
(PVOID)&TokenHandle,
sizeof(TokenHandle));
ASSERT(NT_SUCCESS(NtStatus));
return Status;
} // CreatePlugPlaySecurityObject
VOID
DestroyPlugPlaySecurityObject(
VOID
)
/*++
Routine Description:
This function deletes the self-relative security descriptor which
represents the Plug and Play security object.
Arguments:
None.
Return Value:
None.
--*/
{
if (PlugPlaySecurityObject != NULL) {
RtlDeleteSecurityObject(&PlugPlaySecurityObject);
PlugPlaySecurityObject = NULL;
}
return;
} // DestroyPlugPlaySecutityObject
BOOL
VerifyClientAccess(
IN handle_t hBinding,
IN ACCESS_MASK DesiredAccess
)
/*++
Routine Description:
This routine determines if the client associated with hBinding is granted
trhe desired access.
Arguments:
hBinding RPC Binding handle
DesiredAccess Access desired
Return value:
The return value is TRUE if the client is granted access, FALSE if not or if
an error occurs.
--*/
{
RPC_STATUS rpcStatus;
BOOL AccessStatus = FALSE;
BOOL GenerateOnClose;
ACCESS_MASK GrantedAccess;
//
// If the specified RPC binding handle is NULL, this is an internal call so
// we assume that the access has already been checked.
//
if (hBinding == NULL) {
return TRUE;
}
//
// If we have no security object, we cannot perform access checks, and no
// access is granted.
//
ASSERT(PlugPlaySecurityObject != NULL);
if (PlugPlaySecurityObject == NULL) {
return FALSE;
}
//
// If any generic access rights were specified, map them to the specific and
// standard rights specified in the object's generic access map.
//
MapGenericMask(
(PDWORD)&DesiredAccess,
&PlugPlaySecurityObjectMapping);
//
// Impersonate the client.
//
rpcStatus = RpcImpersonateClient(hBinding);
if (rpcStatus != RPC_S_OK) {
KdPrintEx((DPFLTR_PNPMGR_ID,
DBGF_ERRORS,
"UMPNPMGR: RpcImpersonateClient failed, error = %d\n",
rpcStatus));
return FALSE;
}
//
// Perform the access check while impersonating the client - the
// impersonation token is automatically used. Generate audit and alarm,
// as specified by the security object.
//
// Note that auditing requires the SE_AUDIT_PRIVILEGE to be enabled in the
// *process* token. Most likely, the SCM would have enabled this privilege
// for the services.exe process at startup (since it also performs
// auditing). If not, we would have attempted to enable it during our
// initialization, when we created the PlugPlaySecurityObject.
//
if (!AccessCheckAndAuditAlarm(
PLUGPLAY_SUBSYSTEM_NAME, // subsystem name
NULL, // handle to object
PLUGPLAY_SECURITY_OBJECT_TYPE, // type of object
PLUGPLAY_SECURITY_OBJECT_NAME, // name of object
PlugPlaySecurityObject, // SD
DesiredAccess, // requested access rights
&PlugPlaySecurityObjectMapping, // mapping
FALSE, // creation status
&GrantedAccess, // granted access rights
&AccessStatus, // result of access check
&GenerateOnClose // audit generation option
)) {
KdPrintEx((DPFLTR_PNPMGR_ID,
DBGF_ERRORS,
"UMPNPMGR: AccessCheckAndAuditAlarm failed, error = %d\n",
GetLastError()));
AccessStatus = FALSE;
}
//
// Stop impersonating.
//
rpcStatus = RpcRevertToSelf();
if (rpcStatus != RPC_S_OK) {
KdPrintEx((DPFLTR_PNPMGR_ID,
DBGF_ERRORS,
"UMPNPMGR: RpcRevertToSelf failed, error = %d\n",
rpcStatus));
ASSERT(rpcStatus == RPC_S_OK);
}
return AccessStatus;
} // VerifyClientAccess