/*++ Copyright (c) 1989 Microsoft Corporation Module Name: seurtl.c Abstract: This Module implements many security rtl routines defined in nturtl.h Author: Robert Reichel (RobertRe) 1-Mar-1991 Environment: Pure Runtime Library Routine User mode callable only Revision History: --*/ #include "ldrp.h" #include #include #include // needed for RtlGetPrimaryDomain #include "seopaque.h" #include "sertlp.h" /////////////////////////////////////////////////////////////////////////////// // // // Exported Procedures // // // /////////////////////////////////////////////////////////////////////////////// #if WHEN_LSAUDLL_MOVED_TO_NTDLL NTSTATUS RtlGetPrimaryDomain( IN ULONG SidLength, OUT PBOOLEAN PrimaryDomainPresent, OUT PUNICODE_STRING PrimaryDomainName, OUT PUSHORT RequiredNameLength, OUT PSID PrimaryDomainSid OPTIONAL, OUT PULONG RequiredSidLength ) /*++ Routine Description: This procedure opens the LSA policy object and retrieves the primary domain information for this machine. Arguments: SidLength - Specifies the length of the PrimaryDomainSid parameter. PrimaryDomainPresent - Receives a boolean value indicating whether this machine has a primary domain or not. TRUE indicates the machine does have a primary domain. FALSE indicates the machine does not. PrimaryDomainName - Points to the unicode string to receive the primary domain name. This parameter will only be used if there is a primary domain. RequiredNameLength - Recevies the length of the primary domain name (in bytes). This parameter will only be used if there is a primary domain. PrimaryDomainSid - This optional parameter, if present, points to a buffer to receive the primary domain's SID. This parameter will only be used if there is a primary domain. RequiredSidLength - Recevies the length of the primary domain SID (in bytes). This parameter will only be used if there is a primary domain. Return Value: STATUS_SUCCESS - The requested information has been retrieved. STATUS_BUFFER_TOO_SMALL - One of the return buffers was not large enough to receive the corresponding information. The RequiredNameLength and RequiredSidLength parameter values have been set to indicate the needed length. Other status values as may be returned by: LsaOpenPolicy() LsaQueryInformationPolicy() RtlCopySid() --*/ { NTSTATUS Status, IgnoreStatus; OBJECT_ATTRIBUTES ObjectAttributes; LSA_HANDLE LsaHandle; SECURITY_QUALITY_OF_SERVICE SecurityQualityOfService; PPOLICY_PRIMARY_DOMAIN_INFO PrimaryDomainInfo; // // Set up the Security Quality Of Service // SecurityQualityOfService.Length = sizeof(SECURITY_QUALITY_OF_SERVICE); SecurityQualityOfService.ImpersonationLevel = SecurityImpersonation; SecurityQualityOfService.ContextTrackingMode = SECURITY_DYNAMIC_TRACKING; SecurityQualityOfService.EffectiveOnly = FALSE; // // Set up the object attributes to open the Lsa policy object // InitializeObjectAttributes(&ObjectAttributes, NULL, 0L, (HANDLE)NULL, NULL); ObjectAttributes.SecurityQualityOfService = &SecurityQualityOfService; // // Open the local LSA policy object // Status = LsaOpenPolicy( NULL, &ObjectAttributes, POLICY_VIEW_LOCAL_INFORMATION, &LsaHandle ); if (NT_SUCCESS(Status)) { // // Get the primary domain info // Status = LsaQueryInformationPolicy(LsaHandle, PolicyPrimaryDomainInformation, (PVOID *)&PrimaryDomainInfo); IgnoreStatus = LsaClose(LsaHandle); ASSERT(NT_SUCCESS(IgnoreStatus)); } if (NT_SUCCESS(Status)) { // // Is there a primary domain? // if (PrimaryDomainInfo->Sid != NULL) { // // Yes // (*PrimaryDomainPresent) = TRUE; (*RequiredNameLength) = PrimaryDomainInfo->Name.Length; (*RequiredSidLength) = RtlLengthSid(PrimaryDomainInfo->Sid); // // Copy the name // if (PrimaryDomainName->MaximumLength >= PrimaryDomainInfo->Name.Length) { RtlCopyUnicodeString( PrimaryDomainName, &PrimaryDomainInfo->Name ); } else { Status = STATUS_BUFFER_TOO_SMALL; } // // Copy the SID (if appropriate) // if (PrimaryDomainSid != NULL && NT_SUCCESS(Status)) { Status = RtlCopySid(SidLength, PrimaryDomainSid, PrimaryDomainInfo->Sid ); } } else { (*PrimaryDomainPresent) = FALSE; } // // We're finished with the buffer returned by LSA // IgnoreStatus = LsaFreeMemory(PrimaryDomainInfo); ASSERT(NT_SUCCESS(IgnoreStatus)); } return(Status); } #endif //WHEN_LSAUDLL_MOVED_TO_NTDLL NTSTATUS RtlNewSecurityObjectWithMultipleInheritance ( IN PSECURITY_DESCRIPTOR ParentDescriptor OPTIONAL, IN PSECURITY_DESCRIPTOR CreatorDescriptor OPTIONAL, OUT PSECURITY_DESCRIPTOR * NewDescriptor, IN GUID **pObjectType OPTIONAL, IN ULONG GuidCount, IN BOOLEAN IsDirectoryObject, IN ULONG AutoInheritFlags, IN HANDLE Token OPTIONAL, IN PGENERIC_MAPPING GenericMapping ) /*++ Routine Description: See RtlpNewSecurityObject. - - WARNING - - This service is for use by protected subsystems that project their own type of object. This service is explicitly not for use by the executive for executive objects and must not be called from kernel mode. Arguments: See RtlpNewSecurityObject. Return Value: See RtlpNewSecurityObject. --*/ { // // Simple call the newer RtlpNewSecurityObject // return RtlpNewSecurityObject ( ParentDescriptor, CreatorDescriptor, NewDescriptor, pObjectType, GuidCount, IsDirectoryObject, AutoInheritFlags, Token, GenericMapping ); } NTSTATUS RtlNewSecurityObjectEx ( IN PSECURITY_DESCRIPTOR ParentDescriptor OPTIONAL, IN PSECURITY_DESCRIPTOR CreatorDescriptor OPTIONAL, OUT PSECURITY_DESCRIPTOR * NewDescriptor, IN GUID *ObjectType OPTIONAL, IN BOOLEAN IsDirectoryObject, IN ULONG AutoInheritFlags, IN HANDLE Token OPTIONAL, IN PGENERIC_MAPPING GenericMapping ) /*++ Routine Description: See RtlpNewSecurityObject. - - WARNING - - This service is for use by protected subsystems that project their own type of object. This service is explicitly not for use by the executive for executive objects and must not be called from kernel mode. Arguments: See RtlpNewSecurityObject. Return Value: See RtlpNewSecurityObject. --*/ { // // Simple call the newer RtlpNewSecurityObject // return RtlpNewSecurityObject ( ParentDescriptor, CreatorDescriptor, NewDescriptor, ObjectType ? &ObjectType : NULL, ObjectType ? 1 : 0, IsDirectoryObject, AutoInheritFlags, Token, GenericMapping ); } NTSTATUS RtlNewSecurityObject ( IN PSECURITY_DESCRIPTOR ParentDescriptor OPTIONAL, IN PSECURITY_DESCRIPTOR CreatorDescriptor OPTIONAL, OUT PSECURITY_DESCRIPTOR * NewDescriptor, IN BOOLEAN IsDirectoryObject, IN HANDLE Token, IN PGENERIC_MAPPING GenericMapping ) /*++ Routine Description: See RtlpNewSecurityObject. - - WARNING - - This service is for use by protected subsystems that project their own type of object. This service is explicitly not for use by the executive for executive objects and must not be called from kernel mode. Arguments: See RtlpNewSecurityObject. Return Value: See RtlpNewSecurityObject. --*/ { // // Simple call the newer RtlpNewSecurityObject // return RtlpNewSecurityObject ( ParentDescriptor, CreatorDescriptor, NewDescriptor, NULL, // No ObjectType 0, IsDirectoryObject, 0, // No Automatic inheritance Token, GenericMapping ); } NTSTATUS RtlSetSecurityObject ( IN SECURITY_INFORMATION SecurityInformation, IN PSECURITY_DESCRIPTOR ModificationDescriptor, IN OUT PSECURITY_DESCRIPTOR *ObjectsSecurityDescriptor, IN PGENERIC_MAPPING GenericMapping, IN HANDLE Token OPTIONAL ) /*++ Routine Description: See RtlpSetSecurityObject. Arguments: See RtlpSetSecurityObject. Return Value: See RtlpSetSecurityObject. --*/ { // // Simply call RtlpSetSecurityObject specifying no auto inheritance. // return RtlpSetSecurityObject( NULL, SecurityInformation, ModificationDescriptor, ObjectsSecurityDescriptor, 0, // No AutoInheritance PagedPool, GenericMapping, Token ); } NTSTATUS RtlSetSecurityObjectEx ( IN SECURITY_INFORMATION SecurityInformation, IN PSECURITY_DESCRIPTOR ModificationDescriptor, IN OUT PSECURITY_DESCRIPTOR *ObjectsSecurityDescriptor, IN ULONG AutoInheritFlags, IN PGENERIC_MAPPING GenericMapping, IN HANDLE Token OPTIONAL ) /*++ Routine Description: See RtlpSetSecurityObject. Arguments: See RtlpSetSecurityObject. Return Value: See RtlpSetSecurityObject. --*/ { // // Simply call RtlpSetSecurityObject specifying no auto inheritance. // return RtlpSetSecurityObject( NULL, SecurityInformation, ModificationDescriptor, ObjectsSecurityDescriptor, AutoInheritFlags, PagedPool, GenericMapping, Token ); } NTSTATUS RtlQuerySecurityObject ( IN PSECURITY_DESCRIPTOR ObjectDescriptor, IN SECURITY_INFORMATION SecurityInformation, OUT PSECURITY_DESCRIPTOR ResultantDescriptor, IN ULONG DescriptorLength, OUT PULONG ReturnLength ) /*++ Routine Description: Query information from a protected server object's existing security descriptor. This procedure, called only from user mode, is used to retrieve information from a security descriptor on an existing protected server's object. All access checking is expected to be done before calling this routine. This includes checking for READ_CONTROL, and privilege to read a system ACL as appropriate. - - WARNING - - This service is for use by protected subsystems that project their own type of object. This service is explicitly not for use by the executive for executive objects and must not be called from kernel mode. Arguments: ObjectDescriptor - Points to a pointer to a security descriptor to be queried. SecurityInformation - Identifies the security information being requested. ResultantDescriptor - Points to buffer to receive the resultant security descriptor. The resultant security descriptor will contain all information requested by the SecurityInformation parameter. DescriptorLength - Is an unsigned integer which indicates the length, in bytes, of the buffer provided to receive the resultant descriptor. ReturnLength - Receives an unsigned integer indicating the actual number of bytes needed in the ResultantDescriptor to store the requested information. If the value returned is greater than the value passed via the DescriptorLength parameter, then STATUS_BUFFER_TOO_SMALL is returned and no information is returned. Return Value: STATUS_SUCCESS - The operation was successful. STATUS_BUFFER_TOO_SMALL - The buffer provided to receive the requested information was not large enough to hold the information. No information has been returned. STATUS_BAD_DESCRIPTOR_FORMAT - Indicates the provided object's security descriptor was not in self-relative format. --*/ { PSID Group; PSID Owner; PACL Dacl; PACL Sacl; ULONG GroupSize = 0; ULONG DaclSize = 0; ULONG SaclSize = 0; ULONG OwnerSize = 0; PCHAR Field; PCHAR Base; PISECURITY_DESCRIPTOR IObjectDescriptor; PISECURITY_DESCRIPTOR_RELATIVE IResultantDescriptor; Dacl = NULL; Sacl = NULL; Group = NULL; Owner = NULL; IResultantDescriptor = (PISECURITY_DESCRIPTOR_RELATIVE)ResultantDescriptor; IObjectDescriptor = (PISECURITY_DESCRIPTOR)ObjectDescriptor; // // For each item specified in the SecurityInformation, extract it // and get it to the point where it can be copied into a new // descriptor. // if (SecurityInformation & GROUP_SECURITY_INFORMATION) { Group = RtlpGroupAddrSecurityDescriptor(IObjectDescriptor); if (Group != NULL) { GroupSize = LongAlignSize(SeLengthSid(Group)); } } if (SecurityInformation & DACL_SECURITY_INFORMATION) { Dacl = RtlpDaclAddrSecurityDescriptor( IObjectDescriptor ); if (Dacl != NULL) { DaclSize = LongAlignSize(Dacl->AclSize); } } if (SecurityInformation & SACL_SECURITY_INFORMATION) { Sacl = RtlpSaclAddrSecurityDescriptor( IObjectDescriptor ); if (Sacl != NULL) { SaclSize = LongAlignSize(Sacl->AclSize); } } if (SecurityInformation & OWNER_SECURITY_INFORMATION) { Owner = RtlpOwnerAddrSecurityDescriptor ( IObjectDescriptor ); if (Owner != NULL) { OwnerSize = LongAlignSize(SeLengthSid(Owner)); } } *ReturnLength = sizeof( SECURITY_DESCRIPTOR_RELATIVE ) + GroupSize + DaclSize + SaclSize + OwnerSize; if (*ReturnLength > DescriptorLength) { return( STATUS_BUFFER_TOO_SMALL ); } RtlCreateSecurityDescriptorRelative( IResultantDescriptor, SECURITY_DESCRIPTOR_REVISION ); RtlpSetControlBits( IResultantDescriptor, SE_SELF_RELATIVE ); Base = (PCHAR)(IResultantDescriptor); Field = Base + (ULONG)sizeof(SECURITY_DESCRIPTOR_RELATIVE); if (SecurityInformation & SACL_SECURITY_INFORMATION) { if (SaclSize > 0) { RtlMoveMemory( Field, Sacl, SaclSize ); IResultantDescriptor->Sacl = RtlPointerToOffset(Base,Field); Field += SaclSize; } RtlpPropagateControlBits( IResultantDescriptor, IObjectDescriptor, SE_SACL_PRESENT | SE_SACL_DEFAULTED ); } if (SecurityInformation & DACL_SECURITY_INFORMATION) { if (DaclSize > 0) { RtlMoveMemory( Field, Dacl, DaclSize ); IResultantDescriptor->Dacl = RtlPointerToOffset(Base,Field); Field += DaclSize; } RtlpPropagateControlBits( IResultantDescriptor, IObjectDescriptor, SE_DACL_PRESENT | SE_DACL_DEFAULTED ); } if (SecurityInformation & OWNER_SECURITY_INFORMATION) { if (OwnerSize > 0) { RtlMoveMemory( Field, Owner, OwnerSize ); IResultantDescriptor->Owner = RtlPointerToOffset(Base,Field); Field += OwnerSize; } RtlpPropagateControlBits( IResultantDescriptor, IObjectDescriptor, SE_OWNER_DEFAULTED ); } if (SecurityInformation & GROUP_SECURITY_INFORMATION) { if (GroupSize > 0) { RtlMoveMemory( Field, Group, GroupSize ); IResultantDescriptor->Group = RtlPointerToOffset(Base,Field); } RtlpPropagateControlBits( IResultantDescriptor, IObjectDescriptor, SE_GROUP_DEFAULTED ); } return( STATUS_SUCCESS ); } NTSTATUS RtlDeleteSecurityObject ( IN OUT PSECURITY_DESCRIPTOR * ObjectDescriptor ) /*++ Routine Description: Delete a protected server object's security descriptor. This procedure, called only from user mode, is used to delete a security descriptor associated with a protected server's object. This routine will normally be called by a protected server during object deletion. - - WARNING - - This service is for use by protected subsystems that project their own type of object. This service is explicitly not for use by the executive for executive objects and must not be called from kernel mode. Arguments: ObjectDescriptor - Points to a pointer to a security descriptor to be deleted. Return Value: STATUS_SUCCESS - The operation was successful. --*/ { RtlFreeHeap( RtlProcessHeap(), 0, (PVOID)*ObjectDescriptor ); return( STATUS_SUCCESS ); } NTSTATUS RtlNewInstanceSecurityObject( IN BOOLEAN ParentDescriptorChanged, IN BOOLEAN CreatorDescriptorChanged, IN PLUID OldClientTokenModifiedId, OUT PLUID NewClientTokenModifiedId, IN PSECURITY_DESCRIPTOR ParentDescriptor OPTIONAL, IN PSECURITY_DESCRIPTOR CreatorDescriptor OPTIONAL, OUT PSECURITY_DESCRIPTOR * NewDescriptor, IN BOOLEAN IsDirectoryObject, IN HANDLE Token, IN PGENERIC_MAPPING GenericMapping ) /*++ Routine Description: If the return status is STATUS_SUCCESS and the NewSecurity return value is NULL, then the security desscriptor of the original instance of the object is valid for this instance as well. Arguments: ParentDescriptorChanged - Supplies a flag indicating whether the parent security descriptor has changed since the last time this set of parameters was used. CreatorDescriptorChanged - Supplies a flag indicating whether the creator security descriptor has changed since the last time this set of parameters was used. OldClientTokenModifiedId - Supplies the ModifiedId from the passed token that was in effect when this call was last made with these parameters. If the current ModifiedId is different from the one passed in here, the security descriptor must be rebuilt. NewClientTokenModifiedId - Returns the current ModifiedId from the passed token. ParentDescriptor - Supplies the Security Descriptor for the parent directory under which a new object is being created. If there is no parent directory, then this argument is specified as NULL. CreatorDescriptor - (Optionally) Points to a security descriptor presented by the creator of the object. If the creator of the object did not explicitly pass security information for the new object, then a null pointer should be passed. NewDescriptor - Points to a pointer that is to be made to point to the newly allocated self-relative security descriptor. IsDirectoryObject - Specifies if the new object is going to be a directory object. A value of TRUE indicates the object is a container of other objects. Token - Supplies the token for the client on whose behalf the object is being created. If it is an impersonation token, then it must be at SecurityIdentification level or higher. If it is not an impersonation token, the operation proceeds normally. A client token is used to retrieve default security information for the new object, such as default owner, primary group, and discretionary access control. The token must be open for TOKEN_QUERY access. GenericMapping - Supplies a pointer to a generic mapping array denoting the mapping between each generic right to specific rights. Return Value: return-value - Description of conditions needed to return value. - or - None. --*/ { TOKEN_STATISTICS ClientTokenStatistics; ULONG ReturnLength; NTSTATUS Status; // // Get the current token modified LUID // Status = NtQueryInformationToken( Token, // Handle TokenStatistics, // TokenInformationClass &ClientTokenStatistics, // TokenInformation sizeof(TOKEN_STATISTICS), // TokenInformationLength &ReturnLength // ReturnLength ); if ( !NT_SUCCESS( Status )) { return( Status ); } *NewClientTokenModifiedId = ClientTokenStatistics.ModifiedId; if ( RtlEqualLuid(NewClientTokenModifiedId, OldClientTokenModifiedId) ) { if ( !(ParentDescriptorChanged || CreatorDescriptorChanged) ) { // // The old security descriptor is valid for this new instance // of the object type as well. Pass back success and NULL for // the NewDescriptor. // *NewDescriptor = NULL; return( STATUS_SUCCESS ); } } // // Something has changed, take the long route and build a new // descriptor // return( RtlNewSecurityObject( ParentDescriptor, CreatorDescriptor, NewDescriptor, IsDirectoryObject, Token, GenericMapping )); } NTSTATUS RtlNewSecurityGrantedAccess( IN ACCESS_MASK DesiredAccess, OUT PPRIVILEGE_SET Privileges, IN OUT PULONG Length, IN HANDLE Token OPTIONAL, IN PGENERIC_MAPPING GenericMapping, OUT PACCESS_MASK RemainingDesiredAccess ) /*++ Routine Description: This routine implements privilege policy by examining the bits in a DesiredAccess mask and adjusting them based on privilege checks. Currently, a request for ACCESS_SYSTEM_SECURITY may only be satisfied by the caller having SeSecurityPrivilege. Note that this routine is only to be called when an object is being created. When an object is being opened, it is expected that NtAccessCheck will be called, and that routine will implement another policy for substituting privileges for DACL access. Arguments: DesiredAccess - Supplies the user's desired access mask Privileges - Supplies a pointer to an empty buffer in which will be returned a privilege set describing any privileges that were used to gain access. Note that this is not an optional parameter, that is, enough room for a single privilege must always be passed. Length - Supplies the length of the Privileges parameter in bytes. If the supplies length is not adequate to store the entire privilege set, this field will return the minimum length required. Token - (optionally) Supplies the token for the client on whose behalf the object is being accessed. If this value is specified as null, then the token on the thread is opened and examined to see if it is an impersonation token. If it is, then it must be at SecurityIdentification level or higher. If it is not an impersonation token, the operation proceeds normally. GenericMapping - Supplies the generic mapping associated with this object type. RemainingDesiredAccess - Returns the DesiredAccess mask after any bits have been masked off. If no access types could be granted, this mask will be identical to the one passed in. Return Value: STATUS_SUCCESS - The operation completed successfully. STATUS_BUFFER_TOO_SMALL - The passed buffer was not large enough to contain the information being returned. STATUS_BAD_IMPERSONATION_LEVEL - The caller or passed token was impersonating, but not at a high enough level. --*/ { PRIVILEGE_SET RequiredPrivilege; BOOLEAN Result = FALSE; NTSTATUS Status; HANDLE ThreadToken; BOOLEAN TokenPassed; TOKEN_STATISTICS ThreadTokenStatistics; ULONG ReturnLength; ULONG SizeRequired; ULONG PrivilegeNumber = 0; // // If the caller hasn't passed a token, call the kernel and get // his impersonation token. This call will fail if the caller is // not impersonating a client, so if the caller is not // impersonating someone, he'd better have passed in an explicit // token. // if (!ARGUMENT_PRESENT( Token )) { Status = NtOpenThreadToken( NtCurrentThread(), TOKEN_QUERY, TRUE, &ThreadToken ); TokenPassed = FALSE; if (!NT_SUCCESS( Status )) { return( Status ); } } else { ThreadToken = Token; TokenPassed = TRUE; } Status = NtQueryInformationToken( ThreadToken, // Handle TokenStatistics, // TokenInformationClass &ThreadTokenStatistics, // TokenInformation sizeof(TOKEN_STATISTICS), // TokenInformationLength &ReturnLength // ReturnLength ); ASSERT( NT_SUCCESS(Status) ); RtlMapGenericMask( &DesiredAccess, GenericMapping ); *RemainingDesiredAccess = DesiredAccess; if ( DesiredAccess & ACCESS_SYSTEM_SECURITY ) { RequiredPrivilege.PrivilegeCount = 1; RequiredPrivilege.Control = PRIVILEGE_SET_ALL_NECESSARY; RequiredPrivilege.Privilege[0].Luid = RtlConvertLongToLuid(SE_SECURITY_PRIVILEGE); RequiredPrivilege.Privilege[0].Attributes = 0; // // NtPrivilegeCheck will make sure we are impersonating // properly. // Status = NtPrivilegeCheck( ThreadToken, &RequiredPrivilege, &Result ); if ( (!NT_SUCCESS ( Status )) || (!Result) ) { if (!TokenPassed) { NtClose( ThreadToken ); } if ( !NT_SUCCESS( Status )) { return( Status ); } if ( !Result ) { return( STATUS_PRIVILEGE_NOT_HELD ); } } // // We have the required privilege, turn off the bit in // copy of the input mask and remember that we need to return // this privilege. // *RemainingDesiredAccess &= ~ACCESS_SYSTEM_SECURITY; } if (!TokenPassed) { NtClose( ThreadToken ); } SizeRequired = sizeof(PRIVILEGE_SET); if ( SizeRequired > *Length ) { *Length = SizeRequired; return( STATUS_BUFFER_TOO_SMALL ); } if (Result) { Privileges->PrivilegeCount = 1; Privileges->Control = 0; Privileges->Privilege[PrivilegeNumber].Luid = RtlConvertLongToLuid(SE_SECURITY_PRIVILEGE); Privileges->Privilege[PrivilegeNumber].Attributes = SE_PRIVILEGE_USED_FOR_ACCESS; } else { Privileges->PrivilegeCount = 0; Privileges->Control = 0; Privileges->Privilege[PrivilegeNumber].Luid = RtlConvertLongToLuid(0); Privileges->Privilege[PrivilegeNumber].Attributes = 0; } return( STATUS_SUCCESS ); } NTSTATUS RtlCopySecurityDescriptor( IN PSECURITY_DESCRIPTOR InputSecurityDescriptor, OUT PSECURITY_DESCRIPTOR *OutputSecurityDescriptor ) /*++ Routine Description: This routine will copy a self-relative security descriptor from any memory into the correct type of memory required by security descriptor Rtl routines. This allows security descriptors to be kept in whatever kind of storage is most convenient for the current application. A security descriptor should be copied via this routine and the copy passed into any Rtl routine that in any way modify the security descriptor (eg RtlSetSecurityObject). The storage allocated by this routine must be freed by RtlDeleteSecurityObject. Arguments: InputSecurityDescriptor - contains the source security descriptor OutputSecurityDescriptor - returns a copy of the security descriptor in the correct kind of memory. Return Value: STATUS_NO_MEMORY - There was not enough memory available to the current process to complete this operation. --*/ { PACL Dacl; PACL Sacl; PSID Owner; PSID PrimaryGroup; ULONG DaclSize; ULONG OwnerSize; ULONG PrimaryGroupSize; ULONG SaclSize; ULONG TotalSize; PISECURITY_DESCRIPTOR ISecurityDescriptor = (PISECURITY_DESCRIPTOR)InputSecurityDescriptor; RtlpQuerySecurityDescriptor( ISecurityDescriptor, &Owner, &OwnerSize, &PrimaryGroup, &PrimaryGroupSize, &Dacl, &DaclSize, &Sacl, &SaclSize ); TotalSize = sizeof(SECURITY_DESCRIPTOR_RELATIVE) + OwnerSize + PrimaryGroupSize + DaclSize + SaclSize; *OutputSecurityDescriptor = RtlAllocateHeap( RtlProcessHeap(), MAKE_TAG( SE_TAG ), TotalSize ); if ( *OutputSecurityDescriptor == NULL ) { return( STATUS_NO_MEMORY ); } RtlCopyMemory( *OutputSecurityDescriptor, ISecurityDescriptor, TotalSize ); return( STATUS_SUCCESS ); } NTSTATUS RtlpInitializeAllowedAce( IN PACCESS_ALLOWED_ACE AllowedAce, IN USHORT AceSize, IN UCHAR InheritFlags, IN UCHAR AceFlags, IN ACCESS_MASK Mask, IN PSID AllowedSid ) /*++ Routine Description: This function assigns the specified ACE values into an allowed type ACE. Arguments: AllowedAce - Supplies a pointer to the ACE that is initialized. AceSize - Supplies the size of the ACE in bytes. InheritFlags - Supplies ACE inherit flags. AceFlags - Supplies ACE type specific control flags. Mask - Supplies the allowed access masks. AllowedSid - Supplies the pointer to the SID of user/group which is allowed the specified access. Return Value: Returns status from RtlCopySid. --*/ { AllowedAce->Header.AceType = ACCESS_ALLOWED_ACE_TYPE; AllowedAce->Header.AceSize = AceSize; AllowedAce->Header.AceFlags = AceFlags | InheritFlags; AllowedAce->Mask = Mask; return RtlCopySid( RtlLengthSid(AllowedSid), &(AllowedAce->SidStart), AllowedSid ); } NTSTATUS RtlpInitializeDeniedAce( IN PACCESS_DENIED_ACE DeniedAce, IN USHORT AceSize, IN UCHAR InheritFlags, IN UCHAR AceFlags, IN ACCESS_MASK Mask, IN PSID DeniedSid ) /*++ Routine Description: This function assigns the specified ACE values into a denied type ACE. Arguments: DeniedAce - Supplies a pointer to the ACE that is initialized. AceSize - Supplies the size of the ACE in bytes. InheritFlags - Supplies ACE inherit flags. AceFlags - Supplies ACE type specific control flags. Mask - Supplies the denied access masks. AllowedSid - Supplies the pointer to the SID of user/group which is denied the specified access. Return Value: Returns status from RtlCopySid. --*/ { DeniedAce->Header.AceType = ACCESS_DENIED_ACE_TYPE; DeniedAce->Header.AceSize = AceSize; DeniedAce->Header.AceFlags = AceFlags | InheritFlags; DeniedAce->Mask = Mask; return RtlCopySid( RtlLengthSid(DeniedSid), &(DeniedAce->SidStart), DeniedSid ); } NTSTATUS RtlpInitializeAuditAce( IN PACCESS_ALLOWED_ACE AuditAce, IN USHORT AceSize, IN UCHAR InheritFlags, IN UCHAR AceFlags, IN ACCESS_MASK Mask, IN PSID AuditSid ) /*++ Routine Description: This function assigns the specified ACE values into an audit type ACE. Arguments: AuditAce - Supplies a pointer to the ACE that is initialized. AceSize - Supplies the size of the ACE in bytes. InheritFlags - Supplies ACE inherit flags. AceFlags - Supplies ACE type specific control flags. Mask - Supplies the allowed access masks. AuditSid - Supplies the pointer to the SID of user/group which is to be audited. Return Value: Returns status from RtlCopySid. --*/ { AuditAce->Header.AceType = SYSTEM_AUDIT_ACE_TYPE; AuditAce->Header.AceSize = AceSize; AuditAce->Header.AceFlags = AceFlags | InheritFlags; AuditAce->Mask = Mask; return RtlCopySid( RtlLengthSid(AuditSid), &(AuditAce->SidStart), AuditSid ); } NTSTATUS RtlCreateAndSetSD( IN PRTL_ACE_DATA AceData, IN ULONG AceCount, IN PSID OwnerSid OPTIONAL, IN PSID GroupSid OPTIONAL, OUT PSECURITY_DESCRIPTOR *NewDescriptor ) /*++ Routine Description: This function creates an absolute security descriptor containing the supplied ACE information. A sample usage of this function: // // 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. // RTL_ACE_DATA AceData[4] = { {ACCESS_ALLOWED_ACE_TYPE, 0, 0, GENERIC_ALL, &LocalAdminSid}, {ACCESS_DENIED_ACE_TYPE, 0, 0, GENERIC_ALL, &NetworkSid}, {ACCESS_ALLOWED_ACE_TYPE, 0, 0, WKSTA_CONFIG_GUEST_INFO_GET | WKSTA_CONFIG_USER_INFO_GET, &DomainUsersSid}, {ACCESS_ALLOWED_ACE_TYPE, 0, 0, WKSTA_CONFIG_GUEST_INFO_GET, &DomainGuestsSid} }; PSECURITY_DESCRIPTOR WkstaSecurityDescriptor; return RtlCreateAndSetSD( AceData, 4, LocalSystemSid, LocalSystemSid, &WkstaSecurityDescriptor ); Arguments: AceData - Supplies the structure of information that describes the DACL. AceCount - Supplies the number of entries in AceData structure. OwnerSid - Supplies the pointer to the SID of the security descriptor owner. If not specified, a security descriptor with no owner will be created. GroupSid - Supplies the pointer to the SID of the security descriptor primary group. If not specified, a security descriptor with no primary group will be created. NewDescriptor - Returns a pointer to the absolute security descriptor allocated using RtlAllocateHeap. Return Value: STATUS_SUCCESS - if successful STATUS_NO_MEMORY - if cannot allocate memory for DACL, ACEs, and security descriptor. Any other status codes returned from the security Rtl routines. NOTE : the user security object created by calling this function may be freed up by calling RtlDeleteSecurityObject(). --*/ { NTSTATUS ntstatus = STATUS_SUCCESS; ULONG i; // // Pointer to memory dynamically allocated by this routine to hold // the absolute security descriptor, the DACL, the SACL, and all the ACEs. // // +---------------------------------------------------------------+ // | Security Descriptor | // +-------------------------------+-------+---------------+-------+ // | DACL | ACE 1 | . . . | ACE n | // +-------------------------------+-------+---------------+-------+ // | SACL | ACE 1 | . . . | ACE n | // +-------------------------------+-------+---------------+-------+ // PSECURITY_DESCRIPTOR AbsoluteSd = NULL; PACL Dacl = NULL; // Pointer to the DACL portion of above buffer PACL Sacl = NULL; // Pointer to the SACL portion of above buffer ULONG DaclSize = sizeof(ACL); ULONG SaclSize = sizeof(ACL); ULONG MaxAceSize = 0; PVOID MaxAce = NULL; PCHAR CurrentAvailable; ULONG Size; PVOID HeapHandle = RtlProcessHeap(); ASSERT( AceCount > 0 ); // // Compute the total size of the DACL and SACL ACEs and the maximum // size of any ACE. // for (i = 0; i < AceCount; i++) { ULONG AceSize; AceSize = RtlLengthSid(*(AceData[i].Sid)); switch (AceData[i].AceType) { case ACCESS_ALLOWED_ACE_TYPE: AceSize += sizeof(ACCESS_ALLOWED_ACE); DaclSize += AceSize; break; case ACCESS_DENIED_ACE_TYPE: AceSize += sizeof(ACCESS_DENIED_ACE); DaclSize += AceSize; break; case SYSTEM_AUDIT_ACE_TYPE: AceSize += sizeof(SYSTEM_AUDIT_ACE); SaclSize += AceSize; break; default: return STATUS_INVALID_PARAMETER; } MaxAceSize = MaxAceSize > AceSize ? MaxAceSize : AceSize; } // // Allocate a chunk of memory large enough for the security descriptor, // the DACL, the SACL and all ACEs. // // A security descriptor is of opaque data type but // SECURITY_DESCRIPTOR_MIN_LENGTH is the right size. // Size = SECURITY_DESCRIPTOR_MIN_LENGTH; if ( DaclSize != sizeof(ACL) ) { Size += DaclSize; } if ( SaclSize != sizeof(ACL) ) { Size += SaclSize; } if ((AbsoluteSd = RtlAllocateHeap( HeapHandle, MAKE_TAG( SE_TAG ), Size )) == NULL) { ntstatus = STATUS_NO_MEMORY; goto Cleanup; } // // Initialize the Dacl and Sacl // CurrentAvailable = (PCHAR)AbsoluteSd + SECURITY_DESCRIPTOR_MIN_LENGTH; if ( DaclSize != sizeof(ACL) ) { Dacl = (PACL)CurrentAvailable; CurrentAvailable += DaclSize; ntstatus = RtlCreateAcl( Dacl, DaclSize, ACL_REVISION ); if ( !NT_SUCCESS(ntstatus) ) { goto Cleanup; } } if ( SaclSize != sizeof(ACL) ) { Sacl = (PACL)CurrentAvailable; CurrentAvailable += SaclSize; ntstatus = RtlCreateAcl( Sacl, SaclSize, ACL_REVISION ); if ( !NT_SUCCESS(ntstatus) ) { goto Cleanup; } } // // Allocate a temporary buffer big enough for the biggest ACE. // if ((MaxAce = RtlAllocateHeap( HeapHandle, MAKE_TAG( SE_TAG ), MaxAceSize )) == NULL ) { ntstatus = STATUS_NO_MEMORY; goto Cleanup; } // // Initialize each ACE, and append it into the end of the DACL or SACL. // for (i = 0; i < AceCount; i++) { ULONG AceSize; PACL CurrentAcl; CurrentAcl = NULL; AceSize = RtlLengthSid(*(AceData[i].Sid)); switch (AceData[i].AceType) { case ACCESS_ALLOWED_ACE_TYPE: AceSize += sizeof(ACCESS_ALLOWED_ACE); CurrentAcl = Dacl; ntstatus = RtlpInitializeAllowedAce( MaxAce, (USHORT) AceSize, AceData[i].InheritFlags, AceData[i].AceFlags, AceData[i].Mask, *(AceData[i].Sid) ); break; case ACCESS_DENIED_ACE_TYPE: AceSize += sizeof(ACCESS_DENIED_ACE); CurrentAcl = Dacl; ntstatus = RtlpInitializeDeniedAce( MaxAce, (USHORT) AceSize, AceData[i].InheritFlags, AceData[i].AceFlags, AceData[i].Mask, *(AceData[i].Sid) ); break; case SYSTEM_AUDIT_ACE_TYPE: AceSize += sizeof(SYSTEM_AUDIT_ACE); CurrentAcl = Sacl; ntstatus = RtlpInitializeAuditAce( MaxAce, (USHORT) AceSize, AceData[i].InheritFlags, AceData[i].AceFlags, AceData[i].Mask, *(AceData[i].Sid) ); break; } if ( !NT_SUCCESS( ntstatus ) ) { goto Cleanup; } // // Append the initialized ACE to the end of DACL or SACL // ntstatus = RtlAddAce( CurrentAcl, ACL_REVISION, MAXULONG, MaxAce, AceSize ); if (! NT_SUCCESS ( ntstatus ) ) { goto Cleanup; } } // // Create the security descriptor with absolute pointers to SIDs // and ACLs. // // Owner = OwnerSid // Group = GroupSid // Dacl = Dacl // Sacl = Sacl // if (! NT_SUCCESS(ntstatus = RtlCreateSecurityDescriptor( AbsoluteSd, SECURITY_DESCRIPTOR_REVISION ))) { goto Cleanup; } if (! NT_SUCCESS(ntstatus = RtlSetOwnerSecurityDescriptor( AbsoluteSd, OwnerSid, FALSE ))) { goto Cleanup; } if (! NT_SUCCESS(ntstatus = RtlSetGroupSecurityDescriptor( AbsoluteSd, GroupSid, FALSE ))) { goto Cleanup; } if (! NT_SUCCESS(ntstatus = RtlSetDaclSecurityDescriptor( AbsoluteSd, TRUE, Dacl, FALSE ))) { goto Cleanup; } if (! NT_SUCCESS(ntstatus = RtlSetSaclSecurityDescriptor( AbsoluteSd, Sacl ? TRUE : FALSE, Sacl, FALSE ))) { goto Cleanup; } // // Done // ntstatus = STATUS_SUCCESS; // // Clean up // Cleanup: // // Either return the security descriptor to the caller or delete it // if ( NT_SUCCESS( ntstatus ) ) { *NewDescriptor = AbsoluteSd; } else if ( AbsoluteSd != NULL ) { (void) RtlFreeHeap(HeapHandle, 0, AbsoluteSd); } // // Delete the temporary ACE // if ( MaxAce != NULL ) { (void) RtlFreeHeap(HeapHandle, 0, MaxAce); } return ntstatus; } NTSTATUS RtlCreateUserSecurityObject( IN PRTL_ACE_DATA AceData, IN ULONG AceCount, IN PSID OwnerSid, IN PSID GroupSid, IN BOOLEAN IsDirectoryObject, IN PGENERIC_MAPPING GenericMapping, OUT PSECURITY_DESCRIPTOR *NewDescriptor ) /*++ Routine Description: This function creates the DACL for the security descriptor based on on the ACE information specified, and creates the security descriptor which becomes the user-mode security object. A sample usage of this function: // // Structure that describes the mapping of Generic access rights to // object specific access rights for the ConfigurationInfo object. // GENERIC_MAPPING WsConfigInfoMapping = { STANDARD_RIGHTS_READ | // Generic read WKSTA_CONFIG_GUEST_INFO_GET | WKSTA_CONFIG_USER_INFO_GET | WKSTA_CONFIG_ADMIN_INFO_GET, STANDARD_RIGHTS_WRITE | // Generic write WKSTA_CONFIG_INFO_SET, STANDARD_RIGHTS_EXECUTE, // Generic execute WKSTA_CONFIG_ALL_ACCESS // Generic all }; // // 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. // RTL_ACE_DATA AceData[4] = { {ACCESS_ALLOWED_ACE_TYPE, 0, 0, GENERIC_ALL, &LocalAdminSid}, {ACCESS_DENIED_ACE_TYPE, 0, 0, GENERIC_ALL, &NetworkSid}, {ACCESS_ALLOWED_ACE_TYPE, 0, 0, WKSTA_CONFIG_GUEST_INFO_GET | WKSTA_CONFIG_USER_INFO_GET, &DomainUsersSid}, {ACCESS_ALLOWED_ACE_TYPE, 0, 0, WKSTA_CONFIG_GUEST_INFO_GET, &DomainGuestsSid} }; PSECURITY_DESCRIPTOR WkstaSecurityObject; return RtlCreateUserSecurityObject( AceData, 4, LocalSystemSid, LocalSystemSid, FALSE, &WsConfigInfoMapping, &WkstaSecurityObject ); Arguments: AceData - Supplies the structure of information that describes the DACL. AceCount - Supplies the number of entries in AceData structure. OwnerSid - Supplies the pointer to the SID of the security descriptor owner. GroupSid - Supplies the pointer to the SID of the security descriptor primary group. IsDirectoryObject - Supplies the flag which indicates whether the user-mode object is a directory object. GenericMapping - Supplies the pointer to a generic mapping array denoting the mapping between each generic right to specific rights. NewDescriptor - Returns a pointer to the self-relative security descriptor which represents the user-mode object. Return Value: STATUS_SUCCESS - if successful STATUS_NO_MEMORY - if cannot allocate memory for DACL, ACEs, and security descriptor. Any other status codes returned from the security Rtl routines. NOTE : the user security object created by calling this function may be freed up by calling RtlDeleteSecurityObject(). --*/ { NTSTATUS ntstatus; PSECURITY_DESCRIPTOR AbsoluteSd; HANDLE TokenHandle; PVOID HeapHandle = RtlProcessHeap(); ntstatus = RtlCreateAndSetSD( AceData, AceCount, OwnerSid, GroupSid, &AbsoluteSd ); if (! NT_SUCCESS(ntstatus)) { return ntstatus; } ntstatus = NtOpenProcessToken( NtCurrentProcess(), TOKEN_QUERY, &TokenHandle ); if (! NT_SUCCESS(ntstatus)) { (void) RtlFreeHeap(HeapHandle, 0, AbsoluteSd); return 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, // Parent descriptor AbsoluteSd, // Creator descriptor NewDescriptor, // Pointer to new descriptor IsDirectoryObject, // Is directory object TokenHandle, // Token GenericMapping // Generic mapping ); (void) NtClose(TokenHandle); // // Free dynamic memory before returning // (void) RtlFreeHeap(HeapHandle, 0, AbsoluteSd); return ntstatus; } NTSTATUS RtlConvertToAutoInheritSecurityObject( IN PSECURITY_DESCRIPTOR ParentDescriptor OPTIONAL, IN PSECURITY_DESCRIPTOR CurrentSecurityDescriptor, OUT PSECURITY_DESCRIPTOR *NewSecurityDescriptor, IN GUID *ObjectType OPTIONAL, IN BOOLEAN IsDirectoryObject, IN PGENERIC_MAPPING GenericMapping ) /*++ Routine Description: This is a converts a security descriptor whose ACLs are not marked as AutoInherit to a security descriptor whose ACLs are marked as AutoInherit. See further detailed description on ConvertToAutoInheritPrivateObjectSecurity. Arguments: ParentDescriptor - Supplies the Security Descriptor for the parent directory under which a object exists. If there is no parent directory, then this argument is specified as NULL. CurrentSecurityDescriptor - Supplies a pointer to the objects security descriptor that is going to be altered by this procedure. NewSecurityDescriptor Points to a pointer that is to be made to point to the newly allocated self-relative security descriptor. When no longer needed, this descriptor must be freed using DestroyPrivateObjectSecurity(). ObjectType - GUID of the object type being created. If the object being created has no GUID associated with it, then this argument is specified as NULL. IsDirectoryObject - Specifies if the object is a directory object. A value of TRUE indicates the object is a container of other objects. GenericMapping - Supplies a pointer to a generic mapping array denoting the mapping between each generic right to specific rights. Return Value: STATUS_SUCCESS - The operation was successful. STATUS_UNKNOWN_REVISION - Indicates the source ACL is a revision that is unknown to this routine. (Only revision 2 ACLs are support by this routine.) STATUS_INVALID_ACL - The structure of one of the ACLs in invalid. --*/ { // // Simply call the corresponding Rtlp routine telling it which allocator // to use. // return RtlpConvertToAutoInheritSecurityObject( ParentDescriptor, CurrentSecurityDescriptor, NewSecurityDescriptor, ObjectType, IsDirectoryObject, GenericMapping ); } NTSTATUS RtlDefaultNpAcl( OUT PACL * pAcl ) /*++ Routine Description: This routine constructs a default ACL to be applied to named pipe objects when the caller has not specified one. See NT bug 131090. The ACL constructed is as follows: Need to build an ACL that looks like the following: Local System : F Administrators: F Owner: F Everyone: R Anonymous: R The owner is determined by querying the currently effective token and extracting the default owner. Arguments: pAcl - Receives a pointer to an ACL to apply to the named pipe being created. Guaranteed to be NULL on return if an error occurs. This must be freed by calling RtlFreeHeap. Return Value: NT Status. --*/ { SID_IDENTIFIER_AUTHORITY NtAuthority = SECURITY_NT_AUTHORITY; SID_IDENTIFIER_AUTHORITY WorldSidAuthority = SECURITY_WORLD_SID_AUTHORITY; ULONG AclSize = 0; NTSTATUS Status = STATUS_SUCCESS; ULONG ReturnLength = 0; PTOKEN_OWNER OwnerSid = NULL; HANDLE hToken; // // Initialize OUT parameters // *pAcl = NULL; // // Open thread token // Status = NtOpenThreadToken( NtCurrentThread(), TOKEN_QUERY, TRUE, &hToken ); if (STATUS_NO_TOKEN == Status) { // // Not impersonating, get process token // Status = NtOpenProcessToken( NtCurrentProcess(), TOKEN_QUERY, &hToken ); } if (NT_SUCCESS( Status )) { // // Get the default owner // Status = NtQueryInformationToken ( hToken, TokenOwner, NULL, 0, &ReturnLength ); if (STATUS_BUFFER_TOO_SMALL == Status) { OwnerSid = (PTOKEN_OWNER)RtlAllocateHeap( RtlProcessHeap(), 0, ReturnLength ); if (OwnerSid) { Status = NtQueryInformationToken ( hToken, TokenOwner, OwnerSid, ReturnLength, &ReturnLength ); if (NT_SUCCESS( Status )) { // // Compute the size needed // UCHAR SidBuffer[16]; ASSERT( 16 == RtlLengthRequiredSid( 2 )); AclSize += RtlLengthRequiredSid( 1 ); // LocalSystem Sid AclSize += RtlLengthRequiredSid( 2 ); // Administrators AclSize += RtlLengthRequiredSid( 1 ); // Everyone (World) AclSize += RtlLengthRequiredSid( 1 ); // Anonymous Logon Sid AclSize += RtlLengthSid( OwnerSid->Owner ); // Owner AclSize += sizeof( ACL ); // Header AclSize += 5 * (sizeof( ACCESS_ALLOWED_ACE ) - sizeof( ULONG )); // // Allocate the Acl out of the local process heap // *pAcl = (PACL)RtlAllocateHeap( RtlProcessHeap(), 0, AclSize ); if (*pAcl != NULL) { RtlCreateAcl( *pAcl, AclSize, ACL_REVISION ); // // Create each SID in turn and copy the resultant ACE into // the new ACL // // // Local System - Generic All // RtlInitializeSid( SidBuffer, &NtAuthority, 1); *(RtlSubAuthoritySid( SidBuffer, 0 )) = SECURITY_LOCAL_SYSTEM_RID; RtlAddAccessAllowedAce( *pAcl, ACL_REVISION, GENERIC_ALL, (PSID)SidBuffer ); // // Admins - Generic All // RtlInitializeSid( SidBuffer, &NtAuthority, 2); *(RtlSubAuthoritySid( SidBuffer, 0 )) = SECURITY_BUILTIN_DOMAIN_RID; *(RtlSubAuthoritySid( SidBuffer, 1 )) = DOMAIN_ALIAS_RID_ADMINS; RtlAddAccessAllowedAce( *pAcl, ACL_REVISION, GENERIC_ALL, (PSID)SidBuffer ); // // Owner - Generic All // RtlAddAccessAllowedAce( *pAcl, ACL_REVISION, GENERIC_ALL, OwnerSid->Owner ); // // World - Generic Read // RtlInitializeSid( SidBuffer, &WorldSidAuthority, 1 ); *(RtlSubAuthoritySid( SidBuffer, 0 )) = SECURITY_WORLD_RID; RtlAddAccessAllowedAce( *pAcl, ACL_REVISION, GENERIC_READ, (PSID)SidBuffer ); // // Anonymous Logon - Generic Read // RtlInitializeSid( SidBuffer, &NtAuthority, 1); *(RtlSubAuthoritySid( SidBuffer, 0 )) = SECURITY_ANONYMOUS_LOGON_RID; RtlAddAccessAllowedAce( *pAcl, ACL_REVISION, GENERIC_READ, (PSID)SidBuffer ); } else { Status = STATUS_NO_MEMORY; } } RtlFreeHeap( RtlProcessHeap(), 0, OwnerSid ); } else { Status = STATUS_NO_MEMORY; } } NtClose( hToken ); } if (!NT_SUCCESS( Status )) { // // Something failed, clean up OUT // parameters. // if (*pAcl != NULL) { RtlFreeHeap( RtlProcessHeap(), 0, *pAcl ); *pAcl = NULL; } } return( Status ); }