/*++ Copyright (c) 1991 Microsoft Corporation Module Name: SecDescr.c Abstract: This file contains services related to the establishment of and modification of security descriptors for SAM objects. Note that there are a couple of special security descriptors that this routine does not build. These are the security descriptors for the DOMAIN_ADMIN group, the ADMIN user account, and the SAM object. For the first release, in which creation of domains is not supported, the DOMAIN object's security descriptor is also not created here. These security descriptors are built by the program that initializes a SAM database. Author: Jim Kelly (JimK) 14-Oct-1991 Environment: User Mode - Win32 Revision History: --*/ /////////////////////////////////////////////////////////////////////////////// // // // Includes // // // /////////////////////////////////////////////////////////////////////////////// #include /////////////////////////////////////////////////////////////////////////////// // // // private service prototypes // // // /////////////////////////////////////////////////////////////////////////////// NTSTATUS SampValidatePassedSD( IN ULONG Length, IN PISECURITY_DESCRIPTOR PassedSD ); NTSTATUS SampCheckForDescriptorRestrictions( IN PSAMP_OBJECT Context, IN SAMP_OBJECT_TYPE ObjectType, IN ULONG ObjectRid, IN PISECURITY_DESCRIPTOR PassedSD ); NTSTATUS SampBuildSamProtection( IN PSID WorldSid, IN PSID AdminsAliasSid, IN ULONG AceCount, IN PSID AceSid[], IN ACCESS_MASK AceMask[], IN PGENERIC_MAPPING GenericMap, IN BOOLEAN UserObject, OUT PULONG DescriptorLength, OUT PSECURITY_DESCRIPTOR *Descriptor, OUT PULONG *RidToReplace OPTIONAL ); /////////////////////////////////////////////////////////////////////////////// // // // Services available for use throughout SAM // // // /////////////////////////////////////////////////////////////////////////////// NTSTATUS SampInitializeDomainDescriptors( ULONG Index ) /*++ Routine Description: This routine initializes security descriptors needed to protect user, group, and alias objects. These security descriptors are placed in the SampDefinedDomains[] array. This routine expects all SIDs to be previously initialized. The following security descriptors are prepared: AdminUserSD - Contains a SD appropriate for applying to a user object that is a member of the ADMINISTRATORS alias. AdminGroupSD - Contains a SD appropriate for applying to a group object that is a member of the ADMINISTRATORS alias. NormalUserSD - Contains a SD appropriate for applying to a user object that is NOT a member of the ADMINISTRATORS alias. NormalGroupSD - Contains a SD appropriate for applying to a Group object that is NOT a member of the ADMINISTRATORS alias. NormalAliasSD - Contains a SD appropriate for applying to newly created alias objects. Additionally, the following related information is provided: AdminUserRidPointer NormalUserRidPointer Points to the last RID of the ACE in the corresponding SD's DACL which grants access to the user. This rid must be replaced with the user's rid being the SD is applied to the user object. AdminUserSDLength AdminGroupSDLength NormalUserSDLength NormalGroupSDLength NormalAliasSDLength The length, in bytes, of the corresponding security descriptor. Arguments: Index - The index of the domain whose security descriptors are being created. The Sid field of this domain's data structure is already expected to be set. Return Value: STATUS_SUCCESS - The security descriptors have been successfully initialized. STATUS_INSUFFICIENT_RESOURCES - Heap could not be allocated to produce the needed security descriptors. --*/ { NTSTATUS Status; ULONG Size; PSID AceSid[10]; // Don't expect more than 10 ACEs in any of these. ACCESS_MASK AceMask[10]; // Access masks corresponding to Sids GENERIC_MAPPING AliasMap = {ALIAS_READ, ALIAS_WRITE, ALIAS_EXECUTE, ALIAS_ALL_ACCESS }; GENERIC_MAPPING GroupMap = {GROUP_READ, GROUP_WRITE, GROUP_EXECUTE, GROUP_ALL_ACCESS }; GENERIC_MAPPING UserMap = {USER_READ, USER_WRITE, USER_EXECUTE, USER_ALL_ACCESS }; SID_IDENTIFIER_AUTHORITY BuiltinAuthority = SECURITY_NT_AUTHORITY; ULONG AdminsSidBuffer[8], AccountSidBuffer[8]; PSID AdminsAliasSid = &AdminsSidBuffer[0], AccountAliasSid = &AccountSidBuffer[0], AnySidInAccountDomain = NULL; // // Make sure the buffer we've alloted for the simple sids above // are large enough. // // // ADMINISTRATORS and ACCOUNT_OPERATORS aliases // are is S-1-5-20-x (2 sub-authorities) // ASSERT( RtlLengthRequiredSid(2) <= ( 8 * sizeof(ULONG) ) ); //////////////////////////////////////////////////////////////////////////////////// // // // Initialize the SIDs we'll need. // // //////////////////////////////////////////////////////////////////////////////////// RtlInitializeSid( AdminsAliasSid, &BuiltinAuthority, 2 ); *(RtlSubAuthoritySid( AdminsAliasSid, 0 )) = SECURITY_BUILTIN_DOMAIN_RID; *(RtlSubAuthoritySid( AdminsAliasSid, 1 )) = DOMAIN_ALIAS_RID_ADMINS; RtlInitializeSid( AccountAliasSid, &BuiltinAuthority, 2 ); *(RtlSubAuthoritySid( AccountAliasSid, 0 )) = SECURITY_BUILTIN_DOMAIN_RID; *(RtlSubAuthoritySid( AccountAliasSid, 1 )) = DOMAIN_ALIAS_RID_ACCOUNT_OPS; // // Initialize a SID that can be used to represent accounts // in this domain. // // This is the same as the domain sid found in the DefinedDomains[] // array except it has one more sub-authority. // It doesn't matter what the value of the last RID is because it // is always replaced before use. // Size = RtlLengthSid( SampDefinedDomains[Index].Sid ) + sizeof(ULONG); AnySidInAccountDomain = RtlAllocateHeap( RtlProcessHeap(), 0, Size); ASSERT( AnySidInAccountDomain != NULL ); Status = RtlCopySid( Size, AnySidInAccountDomain, SampDefinedDomains[Index].Sid ); ASSERT(NT_SUCCESS(Status)); (*RtlSubAuthorityCountSid( AnySidInAccountDomain )) += 1; /////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////// // // // // // The following security is assigned to groups that are made // members of the ADMINISTRATORS alias. // // // Owner: Administrators Alias // Group: Administrators Alias // // Dacl: Grant Grant // WORLD Administrators // (Execute | Read) GenericAll // // Sacl: Audit // Success | Fail // WORLD // (Write | Delete | WriteDacl | AccessSystemSecurity) // // // // All other aliases and groups must be assigned the following // security: // // Owner: Administrators Alias // Group: Administrators Alias // // Dacl: Grant Grant Grant // WORLD Administrators AccountOperators Alias // (Execute | Read) GenericAll GenericAll // // Sacl: Audit // Success | Fail // WORLD // (Write | Delete | WriteDacl | AccessSystemSecurity) // // // // // // The following security is assigned to users that are made a // member of the ADMINISTRATORS alias. This includes direct // inclusion or indirect inclusion through group membership. // // // Owner: Administrators Alias // Group: Administrators Alias // // Dacl: Grant Grant Grant // WORLD Administrators User's SID // (Execute | Read) GenericAll GenericWrite // // Sacl: Audit // Success | Fail // WORLD // (Write | Delete | WriteDacl | AccessSystemSecurity) // // // // // All other users must be assigned the following // security: // // Owner: AccountOperators Alias // Group: AccountOperators Alias // // Dacl: Grant Grant Grant Grant // WORLD Administrators Account Operators Alias User's SID // (Execute | Read) GenericAll GenericAll GenericWrite // // Sacl: Audit // Success | Fail // WORLD // (Write | Delete | WriteDacl | AccessSystemSecurity) // // // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! // // Note that because we are going to cram these ACLs directly // into the backing store, we must map the generic accesses // beforehand. // // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! // /////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////// // // We're not particularly good about freeing memory on error // conditions below. Generally speaking, if this doens't // initialize correctly, the system is hosed. // // // Normal Alias SD // AceSid[0] = SampWorldSid; AceMask[0] = (ALIAS_EXECUTE | ALIAS_READ); AceSid[1] = AdminsAliasSid; AceMask[1] = (ALIAS_ALL_ACCESS); AceSid[2] = AccountAliasSid; AceMask[2] = (ALIAS_ALL_ACCESS); Status = SampBuildSamProtection( SampWorldSid, // WorldSid AdminsAliasSid, // AdminsAliasSid 3, // AceCount &AceSid[0], // AceSid array &AceMask[0], // Ace Mask array &AliasMap, // GenericMap FALSE, // Not user object &SampDefinedDomains[Index].NormalAliasSDLength, // Descriptor &SampDefinedDomains[Index].NormalAliasSD, // Descriptor NULL // RidToReplace ); if (!NT_SUCCESS(Status)) { goto done; } // // Admin Group SD // AceSid[0] = SampWorldSid; AceMask[0] = (GROUP_EXECUTE | GROUP_READ); AceSid[1] = AdminsAliasSid; AceMask[1] = (GROUP_ALL_ACCESS); Status = SampBuildSamProtection( SampWorldSid, // WorldSid AdminsAliasSid, // AdminsAliasSid 2, // AceCount &AceSid[0], // AceSid array &AceMask[0], // Ace Mask array &GroupMap, // GenericMap FALSE, // Not user object &SampDefinedDomains[Index].AdminGroupSDLength, // Descriptor &SampDefinedDomains[Index].AdminGroupSD, // Descriptor NULL // RidToReplace ); if (!NT_SUCCESS(Status)) { goto done; } // // Normal GROUP SD // AceSid[0] = SampWorldSid; AceMask[0] = (GROUP_EXECUTE | GROUP_READ); AceSid[1] = AdminsAliasSid; AceMask[1] = (GROUP_ALL_ACCESS); AceSid[2] = AccountAliasSid; AceMask[2] = (GROUP_ALL_ACCESS); Status = SampBuildSamProtection( SampWorldSid, // WorldSid AdminsAliasSid, // AdminsAliasSid 3, // AceCount &AceSid[0], // AceSid array &AceMask[0], // Ace Mask array &GroupMap, // GenericMap FALSE, // Not user object &SampDefinedDomains[Index].NormalGroupSDLength, // Descriptor &SampDefinedDomains[Index].NormalGroupSD, // Descriptor NULL // RidToReplace ); if (!NT_SUCCESS(Status)) { goto done; } // // Admin User SD // AceSid[0] = SampWorldSid; AceMask[0] = (USER_EXECUTE | USER_READ); AceSid[1] = AdminsAliasSid; AceMask[1] = (USER_ALL_ACCESS); AceSid[2] = AnySidInAccountDomain; AceMask[2] = (USER_WRITE); Status = SampBuildSamProtection( SampWorldSid, // WorldSid AdminsAliasSid, // AdminsAliasSid 3, // AceCount &AceSid[0], // AceSid array &AceMask[0], // Ace Mask array &UserMap, // GenericMap TRUE, // Not user object &SampDefinedDomains[Index].AdminUserSDLength, // Descriptor &SampDefinedDomains[Index].AdminUserSD, // Descriptor &SampDefinedDomains[Index].AdminUserRidPointer // RidToReplace ); if (!NT_SUCCESS(Status)) { goto done; } // // Normal User SD // AceSid[0] = SampWorldSid; AceMask[0] = (USER_EXECUTE | USER_READ); AceSid[1] = AdminsAliasSid; AceMask[1] = (USER_ALL_ACCESS); AceSid[2] = AccountAliasSid; AceMask[2] = (USER_ALL_ACCESS); AceSid[3] = AnySidInAccountDomain; AceMask[3] = (USER_WRITE); Status = SampBuildSamProtection( SampWorldSid, // WorldSid AdminsAliasSid, // AdminsAliasSid 4, // AceCount &AceSid[0], // AceSid array &AceMask[0], // Ace Mask array &UserMap, // GenericMap TRUE, // Not user object &SampDefinedDomains[Index].NormalUserSDLength, // Descriptor &SampDefinedDomains[Index].NormalUserSD, // Descriptor &SampDefinedDomains[Index].NormalUserRidPointer // RidToReplace ); if (!NT_SUCCESS(Status)) { goto done; } done: RtlFreeHeap( RtlProcessHeap(), 0, AnySidInAccountDomain ); return(Status); } NTSTATUS SampBuildSamProtection( IN PSID WorldSid, IN PSID AdminsAliasSid, IN ULONG AceCount, IN PSID AceSid[], IN ACCESS_MASK AceMask[], IN PGENERIC_MAPPING GenericMap, IN BOOLEAN UserObject, OUT PULONG DescriptorLength, OUT PSECURITY_DESCRIPTOR *Descriptor, OUT PULONG *RidToReplace OPTIONAL ) /*++ Routine Description: This routine builds a self-relative security descriptor ready to be applied to one of the SAM objects. If so indicated, a pointer to the last RID of the SID in the last ACE of the DACL is returned and a flag set indicating that the RID must be replaced before the security descriptor is applied to an object. This is to support USER object protection, which must grant some access to the user represented by the object. The owner and group of each security descriptor will be set to: Owner: Administrators Alias Group: Administrators Alias The SACL of each of these objects will be set to: Audit Success | Fail WORLD (Write | Delete | WriteDacl | AccessSystemSecurity) Arguments: AceCount - The number of ACEs to be included in the DACL. AceSid - Points to an array of SIDs to be granted access by the DACL. If the target SAM object is a User object, then the last entry in this array is expected to be the SID of an account within the domain with the last RID not yet set. The RID will be set during actual account creation. AceMask - Points to an array of accesses to be granted by the DACL. The n'th entry of this array corresponds to the n'th entry of the AceSid array. These masks should not include any generic access types. GenericMap - Points to a generic mapping for the target object type. UserObject - Indicates whether the target SAM object is a User object or not. If TRUE (it is a User object), then the resultant protection will be set up indicating Rid replacement is necessary. DescriptorLength - Receives the length of the resultant SD. Descriptor - Receives a pointer to the resultant SD. RidToReplace - Is required aif userObject is TRUE and will be set to point to the user's RID. Return Value: TBS. --*/ { NTSTATUS Status; SECURITY_DESCRIPTOR Absolute; PSECURITY_DESCRIPTOR Relative; PACL TmpAcl; PACCESS_ALLOWED_ACE TmpAce; PSID TmpSid; ULONG Length, i; PULONG RidLocation; BOOLEAN IgnoreBoolean; ACCESS_MASK MappedMask; // // The approach is to set up an absolute security descriptor that // looks like what we want and then copy it to make a self-relative // security descriptor. // Status = RtlCreateSecurityDescriptor( &Absolute, SECURITY_DESCRIPTOR_REVISION1 ); ASSERT( NT_SUCCESS(Status) ); // // Owner // Status = RtlSetOwnerSecurityDescriptor (&Absolute, AdminsAliasSid, FALSE ); ASSERT(NT_SUCCESS(Status)); // // Group // Status = RtlSetGroupSecurityDescriptor (&Absolute, AdminsAliasSid, FALSE ); ASSERT(NT_SUCCESS(Status)); // // Discretionary ACL // // Calculate its length, // Allocate it, // Initialize it, // Add each ACE // Add it to the security descriptor // Length = (ULONG)sizeof(ACL); for (i=0; iGenericWrite | DELETE | WRITE_DAC | ACCESS_SYSTEM_SECURITY, WorldSid, TRUE, //AuditSuccess, TRUE //AuditFailure ); ASSERT( NT_SUCCESS(Status) ); Status = RtlSetSaclSecurityDescriptor (&Absolute, TRUE, TmpAcl, FALSE ); ASSERT(NT_SUCCESS(Status)); // // Convert the Security Descriptor to Self-Relative // // Get the length needed // Allocate that much memory // Copy it // Free the generated absolute ACLs // Length = 0; Status = RtlAbsoluteToSelfRelativeSD( &Absolute, NULL, &Length ); ASSERT(Status == STATUS_BUFFER_TOO_SMALL); Relative = RtlAllocateHeap( RtlProcessHeap(), 0, Length ); ASSERT(Relative != NULL); Status = RtlAbsoluteToSelfRelativeSD(&Absolute, Relative, &Length ); ASSERT(NT_SUCCESS(Status)); RtlFreeHeap( RtlProcessHeap(), 0, Absolute.Dacl ); RtlFreeHeap( RtlProcessHeap(), 0, Absolute.Sacl ); // // If the object is a user object, then get the address of the // last RID of the SID in the last ACE in the DACL. // if (UserObject == TRUE) { Status = RtlGetDaclSecurityDescriptor( Relative, &IgnoreBoolean, &TmpAcl, &IgnoreBoolean ); ASSERT(NT_SUCCESS(Status)); Status = RtlGetAce ( TmpAcl, AceCount-1, (PVOID *)&TmpAce ); ASSERT(NT_SUCCESS(Status)); TmpSid = (PSID)(&TmpAce->SidStart), RidLocation = RtlSubAuthoritySid( TmpSid, (ULONG)(*RtlSubAuthorityCountSid( TmpSid ) - 1) ); } // // Set the result information // (*DescriptorLength) = Length; (*Descriptor) = Relative; if (ARGUMENT_PRESENT(RidToReplace)) { (*RidToReplace) = RidLocation; } return(Status); } NTSTATUS SampGetNewAccountSecurity( IN SAMP_OBJECT_TYPE ObjectType, IN BOOLEAN Admin, IN BOOLEAN TrustedClient, IN BOOLEAN RestrictCreatorAccess, IN ULONG NewAccountRid, OUT PSECURITY_DESCRIPTOR *NewDescriptor, OUT PULONG DescriptorLength ) /*++ Routine Description: This service creates a standard self-relative security descriptor for a new USER, GROUP or ALIAS account. Note: THIS ROUTINE REFERENCES THE CURRENT TRANSACTION DOMAIN (ESTABLISHED USING SampSetTransactioDomain()). THIS SERVICE MAY ONLY BE CALLED AFTER SampSetTransactionDomain() AND BEFORE SampReleaseWriteLock(). Arguments: ObjectType - Indicates the type of account for which a new security descriptor is required. This must be either SampGroupObjectType or SampUserObjectType. Admin - if TRUE, indicates the security descriptor will be protecting an object that is an admin object (e.g., is a member, directly or indirectly, of the ADMINISTRATORS alias). TrustedClient - Indicates whether the client is a trusted client or not. TRUE indicates the client is trusted, FALSE indicates the client is not trusted. RestrictCreatorAccess - Indicates whether or not the creator's access to the object is to be restricted according to specific rules. Also indicates whether or not the account is to be given any access to itself. An account will only be given access to itself if there are no creator access restrictions. The following ObjectTypes have restriction rules that may be requested: User: - Admin is assigned as owner of the object. - Creator is given (DELETE | USER_WRITE) access. NewAccountRid - The relative ID of the new account. NewDescriptor - Receives a pointer to the new account's self-relative security descriptor. Be sure to free this descriptor with MIDL_user_free() when done. DescriptorLength - Receives the length (in bytes) of the returned security descriptor Return Value: STATUS_SUCCESS - A new security descriptor has been produced. STATUS_INSUFFICIENT_RESOURCES - Memory could not be allocated to produce the security descriptor. --*/ { SID_IDENTIFIER_AUTHORITY BuiltinAuthority = SECURITY_NT_AUTHORITY; ULONG AccountSidBuffer[8]; PSID AccountAliasSid = &AccountSidBuffer[0]; SECURITY_DESCRIPTOR DaclDescriptor; NTSTATUS NtStatus = STATUS_SUCCESS; NTSTATUS IgnoreStatus; HANDLE ClientToken = INVALID_HANDLE_VALUE; ULONG DataLength = 0; ACCESS_ALLOWED_ACE *NewAce = NULL; PACL NewDacl = NULL; PACL OldDacl = NULL; PSECURITY_DESCRIPTOR StaticDescriptor = NULL; PSECURITY_DESCRIPTOR LocalDescriptor = NULL; PTOKEN_GROUPS ClientGroups = NULL; PTOKEN_OWNER SubjectOwner = NULL; PSID SubjectSid = NULL; ULONG AceLength = 0; ULONG i; BOOLEAN AdminAliasFound = FALSE; BOOLEAN AccountAliasFound = FALSE; BOOLEAN DaclPresent, DaclDefaulted; GENERIC_MAPPING GenericMapping; GENERIC_MAPPING AliasMap = {ALIAS_READ, ALIAS_WRITE, ALIAS_EXECUTE, ALIAS_ALL_ACCESS }; GENERIC_MAPPING GroupMap = {GROUP_READ, GROUP_WRITE, GROUP_EXECUTE, GROUP_ALL_ACCESS }; GENERIC_MAPPING UserMap = {USER_READ, USER_WRITE, USER_EXECUTE, USER_ALL_ACCESS }; // // Security account objects don't pick up security in the normal // fashion in the release 1 timeframe. They are assigned a well-known // security descriptor based upon their object type. // // Notice that all the accounts with tricky security are created when // the domain is created (e.g., admin groups and admin user account). // switch (ObjectType) { case SampGroupObjectType: ASSERT(RestrictCreatorAccess == FALSE); // // NewAccountRid parameter is ignored for groups. // if (Admin == TRUE) { StaticDescriptor = SampDefinedDomains[SampTransactionDomainIndex].AdminGroupSD; (*DescriptorLength) = SampDefinedDomains[SampTransactionDomainIndex].AdminGroupSDLength; } else { StaticDescriptor = SampDefinedDomains[SampTransactionDomainIndex].NormalGroupSD; (*DescriptorLength) = SampDefinedDomains[SampTransactionDomainIndex].NormalGroupSDLength; } GenericMapping = GroupMap; break; case SampAliasObjectType: ASSERT(RestrictCreatorAccess == FALSE); // // Admin and NewAccountRid parameters are ignored for aliases. // StaticDescriptor = SampDefinedDomains[SampTransactionDomainIndex].NormalAliasSD; (*DescriptorLength) = SampDefinedDomains[SampTransactionDomainIndex].NormalAliasSDLength; GenericMapping = AliasMap; break; case SampUserObjectType: if (Admin == TRUE) { StaticDescriptor = SampDefinedDomains[SampTransactionDomainIndex].AdminUserSD; (*DescriptorLength) = SampDefinedDomains[SampTransactionDomainIndex].AdminUserSDLength; (*SampDefinedDomains[SampTransactionDomainIndex].AdminUserRidPointer) = NewAccountRid; } else { StaticDescriptor = SampDefinedDomains[SampTransactionDomainIndex].NormalUserSD; (*DescriptorLength) = SampDefinedDomains[SampTransactionDomainIndex].NormalUserSDLength; (*SampDefinedDomains[SampTransactionDomainIndex].NormalUserRidPointer) = NewAccountRid; } GenericMapping = UserMap; break; } // // We have a pointer to SAM's static security descriptor. Copy it // into a heap buffer that RtlSetSecurityObject() will like. // LocalDescriptor = RtlAllocateHeap( RtlProcessHeap(), 0, (*DescriptorLength) ); if ( LocalDescriptor == NULL ) { (*NewDescriptor) = NULL; (*DescriptorLength) = 0; return(STATUS_INSUFFICIENT_RESOURCES); } RtlCopyMemory( LocalDescriptor, StaticDescriptor, (*DescriptorLength) ); // // If the caller is to have restricted access to this account, // then remove the last ACE from the ACL (the one intended for // the account itself). // if (RestrictCreatorAccess) { NtStatus = RtlGetDaclSecurityDescriptor( LocalDescriptor, &DaclPresent, &OldDacl, &DaclDefaulted ); ASSERT(NT_SUCCESS(NtStatus)); ASSERT(DaclPresent); ASSERT(OldDacl->AceCount >= 1); OldDacl->AceCount -= 1; // Remove the last ACE from the ACL. } // // If the caller is not a trusted client, see if the caller is an // administrator or an account operator. If not, add an ACCESS_ALLOWED // ACE to the DACL that gives full access to the creator (or restricted // access, if so specified). // if ( !TrustedClient ) { NtStatus = I_RpcMapWin32Status(RpcImpersonateClient( NULL )); if (NT_SUCCESS(NtStatus)) { // if (ImpersonatingClient) NtStatus = NtOpenThreadToken( NtCurrentThread(), TOKEN_QUERY, TRUE, //OpenAsSelf &ClientToken ); // // Stop impersonating the client // IgnoreStatus = I_RpcMapWin32Status(RpcRevertToSelf()); ASSERT( NT_SUCCESS(IgnoreStatus) ); if (NT_SUCCESS(NtStatus)) { // if (TokenOpened) // // See if the caller is an administrator or an account // operator. First, see how big // a buffer we need to hold the caller's groups. // NtStatus = NtQueryInformationToken( ClientToken, TokenGroups, NULL, 0, &DataLength ); if ( ( NtStatus == STATUS_BUFFER_TOO_SMALL ) && ( DataLength > 0 ) ) { ClientGroups = MIDL_user_allocate( DataLength ); if ( ClientGroups == NULL ) { NtStatus = STATUS_INSUFFICIENT_RESOURCES; } else { // // Now get a list of the caller's groups. // NtStatus = NtQueryInformationToken( ClientToken, TokenGroups, ClientGroups, DataLength, &DataLength ); if ( NT_SUCCESS( NtStatus ) ) { // // Build the SID of the ACCOUNT_OPS alias, so we // can see if the user is included in it. // RtlInitializeSid( AccountAliasSid, &BuiltinAuthority, 2 ); *(RtlSubAuthoritySid( AccountAliasSid, 0 )) = SECURITY_BUILTIN_DOMAIN_RID; *(RtlSubAuthoritySid( AccountAliasSid, 1 )) = DOMAIN_ALIAS_RID_ACCOUNT_OPS; // // See if the ADMIN or ACCOUNT_OPS alias is in // the caller's groups. // for ( i = 0; i < ClientGroups->GroupCount; i++ ) { SubjectSid = ClientGroups->Groups[i].Sid; ASSERT( SubjectSid != NULL ); if ( RtlEqualSid( SubjectSid, SampAdministratorsAliasSid ) ) { AdminAliasFound = TRUE; break; } if ( RtlEqualSid( SubjectSid, AccountAliasSid ) ) { AccountAliasFound = TRUE; break; } } // // If the callers groups did not include the admins // alias, add an ACCESS_ALLOWED ACE for the owner. // if ( !AdminAliasFound && !AccountAliasFound ) { // // First, find out what size buffer we need // to get the owner. // NtStatus = NtQueryInformationToken( ClientToken, TokenOwner, NULL, 0, &DataLength ); if ( ( NtStatus == STATUS_BUFFER_TOO_SMALL ) && ( DataLength > 0 ) ) { SubjectOwner = MIDL_user_allocate( DataLength ); if ( SubjectOwner == NULL ) { NtStatus = STATUS_INSUFFICIENT_RESOURCES; } else { // // Now, query the owner that will be // given access to the object // created. // NtStatus = NtQueryInformationToken( ClientToken, TokenOwner, SubjectOwner, DataLength, &DataLength ); if ( NT_SUCCESS( NtStatus ) ) { // // Create an ACE that gives the // owner full access. // AceLength = sizeof( ACE_HEADER ) + sizeof( ACCESS_MASK ) + RtlLengthSid( SubjectOwner->Owner ); NewAce = (ACCESS_ALLOWED_ACE *) MIDL_user_allocate( AceLength ); if ( NewAce == NULL ) { NtStatus = STATUS_INSUFFICIENT_RESOURCES; } else { NewAce->Header.AceType = ACCESS_ALLOWED_ACE_TYPE; NewAce->Header.AceSize = (USHORT) AceLength; NewAce->Header.AceFlags = 0; NewAce->Mask = USER_ALL_ACCESS; // // If the creator's access is // to be restricted, change the // AccessMask. // if (RestrictCreatorAccess) { NewAce->Mask = DELETE | USER_WRITE | USER_FORCE_PASSWORD_CHANGE; } RtlCopySid( RtlLengthSid( SubjectOwner->Owner ), (PSID)( &NewAce->SidStart ), SubjectOwner->Owner ); // // Allocate a new, larger ACL and // copy the old one into it. // NtStatus = RtlGetDaclSecurityDescriptor( LocalDescriptor, &DaclPresent, &OldDacl, &DaclDefaulted ); if ( NT_SUCCESS( NtStatus ) ) { NewDacl = MIDL_user_allocate( OldDacl->AclSize + AceLength ); if ( NewDacl == NULL ) { NtStatus = STATUS_INSUFFICIENT_RESOURCES; } else { RtlCopyMemory( NewDacl, OldDacl, OldDacl->AclSize ); NewDacl->AclSize = OldDacl->AclSize + (USHORT) AceLength; // // Add the new ACE // to the new ACL. // NtStatus = RtlAddAce( NewDacl, ACL_REVISION2, 1, // add after first ACE (world) (PVOID)NewAce, AceLength ); } // end_if (allocated NewDacl) } // end_if (get DACL from SD) } // end_if (allocated NewAce) } // end_if (Query TokenOwner Succeeded) } // end_if (Allocated TokenOwner buffer) } // end_if (Query TokenOwner size Succeeded) } // end_if (not admin) } // end_if (Query TokenGroups Succeeded) } // end_if (Allocated TokenGroups buffer) } // end_if (Query TokenGroups size Succeeded) IgnoreStatus = NtClose( ClientToken ); ASSERT(NT_SUCCESS(IgnoreStatus)); } // end_if (TokenOpened) } // end_if (ImpersonatingClient) } // end_if (TrustedClient) if ( NT_SUCCESS( NtStatus ) ) { // // If we created a new DACL above, stick it on the security // descriptor. // if ( NewDacl != NULL ) { NtStatus = RtlCreateSecurityDescriptor( &DaclDescriptor, SECURITY_DESCRIPTOR_REVISION1 ); if ( NT_SUCCESS( NtStatus ) ) { // // Set the DACL on the LocalDescriptor. Note that this // call will RtlFreeHeap() the old descriptor, and allocate // a new one. // DaclDescriptor.Control = SE_DACL_PRESENT; DaclDescriptor.Dacl = NewDacl; NtStatus = RtlSetSecurityObject( DACL_SECURITY_INFORMATION, &DaclDescriptor, &LocalDescriptor, &GenericMapping, NULL ); } } } if ( NT_SUCCESS( NtStatus ) ) { // // Copy the security descriptor and length into buffers for the // caller. AceLength is 0 if we didn't add an ACE to the DACL // above. // (*DescriptorLength) = (*DescriptorLength) + AceLength; (*NewDescriptor) = MIDL_user_allocate( (*DescriptorLength) ); if ( (*NewDescriptor) == NULL ) { NtStatus = STATUS_INSUFFICIENT_RESOURCES; } else { RtlCopyMemory( (*NewDescriptor), LocalDescriptor, (*DescriptorLength) ); } } // // Free up local items that may have been allocated. // if ( LocalDescriptor != NULL ) { RtlFreeHeap( RtlProcessHeap(), 0, LocalDescriptor ); } if ( ClientGroups != NULL ) { MIDL_user_free( ClientGroups ); } if ( SubjectOwner != NULL ) { MIDL_user_free( SubjectOwner ); } if ( NewAce != NULL ) { MIDL_user_free( NewAce ); } if ( NewDacl != NULL ) { MIDL_user_free( NewDacl ); } if ( !NT_SUCCESS( NtStatus ) ) { (*NewDescriptor) = NULL; (*DescriptorLength) = 0; } return( NtStatus ); } NTSTATUS SampModifyAccountSecurity( IN SAMP_OBJECT_TYPE ObjectType, IN BOOLEAN Admin, IN PSECURITY_DESCRIPTOR OldDescriptor, OUT PSECURITY_DESCRIPTOR *NewDescriptor, OUT PULONG DescriptorLength ) /*++ Routine Description: This service modifies a self-relative security descriptor for a USER or GROUP to add or remove account operator access. Arguments: ObjectType - Indicates the type of account for which a new security descriptor is required. This must be either SampGroupObjectType or SampUserObjectType. Admin - if TRUE, indicates the security descriptor will be protecting an object that is an admin object (e.g., is a member, directly or indirectly, of the ADMINISTRATORS or an operator alias). NewDescriptor - Receives a pointer to the new account's self-relative security descriptor. Be sure to free this descriptor with MIDL_user_free() when done. DescriptorLength - Receives the length (in bytes) of the returned security descriptor Return Value: STATUS_SUCCESS - A new security descriptor has been produced. STATUS_INSUFFICIENT_RESOURCES - Memory could not be allocated to produce the security descriptor. --*/ { SID_IDENTIFIER_AUTHORITY BuiltinAuthority = SECURITY_NT_AUTHORITY; ULONG AccountSidBuffer[8]; PSID AccountAliasSid = &AccountSidBuffer[0]; NTSTATUS NtStatus = STATUS_SUCCESS; NTSTATUS IgnoreStatus; ULONG Length; ULONG i,j; ULONG AccountOpAceIndex; ULONG AceCount; PACL OldDacl; PACL NewDacl = NULL; BOOLEAN DaclDefaulted; BOOLEAN DaclPresent; ACL_SIZE_INFORMATION AclSizeInfo; PACCESS_ALLOWED_ACE Ace; PGENERIC_MAPPING GenericMapping; ACCESS_MASK AccountOpAccess; SECURITY_DESCRIPTOR AbsoluteDescriptor; PSECURITY_DESCRIPTOR LocalDescriptor = NULL; GENERIC_MAPPING GroupMap = {GROUP_READ, GROUP_WRITE, GROUP_EXECUTE, GROUP_ALL_ACCESS }; GENERIC_MAPPING UserMap = {USER_READ, USER_WRITE, USER_EXECUTE, USER_ALL_ACCESS }; NtStatus = RtlCopySecurityDescriptor( OldDescriptor, &LocalDescriptor ); if (!NT_SUCCESS(NtStatus)) { goto Cleanup; } // // Build the SID of the ACCOUNT_OPS alias, so we // can see if is in the DACL or we can add it to the DACL. // RtlInitializeSid( AccountAliasSid, &BuiltinAuthority, 2 ); *(RtlSubAuthoritySid( AccountAliasSid, 0 )) = SECURITY_BUILTIN_DOMAIN_RID; *(RtlSubAuthoritySid( AccountAliasSid, 1 )) = DOMAIN_ALIAS_RID_ACCOUNT_OPS; // // The approach is to set up an absolute security descriptor that // contains the new DACL, and then merge that into the existing // security descriptor. // IgnoreStatus = RtlCreateSecurityDescriptor( &AbsoluteDescriptor, SECURITY_DESCRIPTOR_REVISION1 ); ASSERT( NT_SUCCESS(IgnoreStatus) ); // // Figure out the access granted to account operators and the // generic mask to use. // if (ObjectType == SampUserObjectType) { AccountOpAccess = USER_ALL_ACCESS; GenericMapping = &UserMap; } else if (ObjectType == SampGroupObjectType) { AccountOpAccess = GROUP_ALL_ACCESS; GenericMapping = &GroupMap; } else { // // This doesn't apply to aliases, domains, or servers. // return(STATUS_INVALID_PARAMETER); } // // Get the old DACL off the passed in security descriptor. // IgnoreStatus = RtlGetDaclSecurityDescriptor( OldDescriptor, &DaclPresent, &OldDacl, &DaclDefaulted ); ASSERT(NT_SUCCESS(IgnoreStatus)); // // We will only modify the DACL if it is present // if (!DaclPresent) { *NewDescriptor = LocalDescriptor; *DescriptorLength = RtlLengthSecurityDescriptor(LocalDescriptor); return(STATUS_SUCCESS); } // // Get the count of ACEs // IgnoreStatus = RtlQueryInformationAcl( OldDacl, &AclSizeInfo, sizeof(AclSizeInfo), AclSizeInformation ); ASSERT(NT_SUCCESS(IgnoreStatus)); // // Calculate the lenght of the new ACL. // Length = (ULONG)sizeof(ACL); AccountOpAceIndex = 0xffffffff; for (i = 0; i < AclSizeInfo.AceCount; i++) { IgnoreStatus = RtlGetAce( OldDacl, i, (PVOID *) &Ace ); ASSERT(NT_SUCCESS(IgnoreStatus)); // // Check if this is an access allowed ACE, and the ACE is for // the Account Operators alias. // if ( (Ace->Header.AceType == ACCESS_ALLOWED_ACE_TYPE) && RtlEqualSid( AccountAliasSid, &Ace->SidStart ) ) { AccountOpAceIndex = i; continue; } Length += Ace->Header.AceSize; } if (!Admin) { // // If we are making this account not be an admin account and it already // has an account operator ace, we are done. // if ( AccountOpAceIndex != 0xffffffff ) { *NewDescriptor = LocalDescriptor; *DescriptorLength = RtlLengthSecurityDescriptor(LocalDescriptor); return(STATUS_SUCCESS); } else { // // Add the size of an account operator ace to the required length // Length += sizeof(ACCESS_ALLOWED_ACE) + RtlLengthSid(AccountAliasSid) - sizeof(ULONG); } } NewDacl = RtlAllocateHeap( RtlProcessHeap(), 0, Length ); if (NewDacl == NULL) { NtStatus = STATUS_INSUFFICIENT_RESOURCES; goto Cleanup; } IgnoreStatus = RtlCreateAcl( NewDacl, Length, ACL_REVISION2); ASSERT( NT_SUCCESS(IgnoreStatus) ); // // Add the old ACEs back into this ACL. // for (i = 0, j = 0; i < AclSizeInfo.AceCount; i++) { if (i == AccountOpAceIndex) { ASSERT(Admin); continue; } // // Add back in the old ACEs // IgnoreStatus = RtlGetAce( OldDacl, i, (PVOID *) &Ace ); ASSERT(NT_SUCCESS(IgnoreStatus)); IgnoreStatus = RtlAddAce ( NewDacl, ACL_REVISION2, j, Ace, Ace->Header.AceSize ); ASSERT( NT_SUCCESS(IgnoreStatus) ); } // // If we are making this account not be an administrator, add the // access allowed ACE for the account operator. This ACE is always // the second to last one. // if (!Admin) { IgnoreStatus = RtlAddAccessAllowedAce( NewDacl, ACL_REVISION2, AccountOpAccess, AccountAliasSid ); ASSERT(NT_SUCCESS(IgnoreStatus)); } // // Insert this DACL into the security descriptor. // IgnoreStatus = RtlSetDaclSecurityDescriptor ( &AbsoluteDescriptor, TRUE, // DACL present NewDacl, FALSE // DACL not defaulted ); ASSERT(NT_SUCCESS(IgnoreStatus)); // // Now call RtlSetSecurityObject to merge the existing security descriptor // with the new DACL we just created. // NtStatus = RtlSetSecurityObject( DACL_SECURITY_INFORMATION, &AbsoluteDescriptor, &LocalDescriptor, GenericMapping, NULL ); if (!NT_SUCCESS(NtStatus)) { goto Cleanup; } *NewDescriptor = LocalDescriptor; *DescriptorLength = RtlLengthSecurityDescriptor(LocalDescriptor); LocalDescriptor = NULL; Cleanup: if ( NewDacl != NULL ) { RtlFreeHeap(RtlProcessHeap(),0, NewDacl ); } if (LocalDescriptor != NULL) { RtlDeleteSecurityObject(&LocalDescriptor); } return( NtStatus ); } NTSTATUS SampGetObjectSD( IN PSAMP_OBJECT Context, OUT PULONG SecurityDescriptorLength, OUT PSECURITY_DESCRIPTOR *SecurityDescriptor ) /*++ Routine Description: This retrieves a security descriptor from a SAM object's backing store. Arguments: Context - The object to which access is being requested. SecurityDescriptorLength - Receives the length of the security descriptor. SecurityDescriptor - Receives a pointer to the security descriptor. Return Value: STATUS_SUCCESS - The security descriptor has been retrieved. STATUS_INTERNAL_DB_CORRUPTION - The object does not have a security descriptor. This is bad. STATUS_INSUFFICIENT_RESOURCES - Memory could not be allocated to retrieve the security descriptor. STATUS_UNKNOWN_REVISION - The security descriptor retrieved is no one known by this revision of SAM. --*/ { NTSTATUS NtStatus; ULONG Revision; (*SecurityDescriptorLength) = 0; NtStatus = SampGetAccessAttribute( Context, SAMP_OBJECT_SECURITY_DESCRIPTOR, TRUE, // Make copy &Revision, SecurityDescriptor ); if (NT_SUCCESS(NtStatus)) { if ( ((Revision && 0xFFFF0000) > SAMP_MAJOR_REVISION) || (Revision > SAMP_REVISION) ) { NtStatus = STATUS_UNKNOWN_REVISION; } if (!NT_SUCCESS(NtStatus)) { MIDL_user_free( (*SecurityDescriptor) ); } } if (NT_SUCCESS(NtStatus)) { *SecurityDescriptorLength = RtlLengthSecurityDescriptor( (*SecurityDescriptor) ); } return(NtStatus); } NTSTATUS SamrSetSecurityObject( IN SAMPR_HANDLE ObjectHandle, IN SECURITY_INFORMATION SecurityInformation, IN PSAMPR_SR_SECURITY_DESCRIPTOR SecurityDescriptor ) /*++ Routine Description: This function (SamrSetSecurityObject) takes a well formed Security Descriptor provided by the caller and assigns specified portions of it to an object. Based on the flags set in the SecurityInformation parameter and the caller's access rights, this procedure will replace any or all of the security information associated with an object. This is the only function available to users and applications for changing security information, including the owner ID, group ID, and the discretionary and system ACLs of an object. The caller must have WRITE_OWNER access to the object to change the owner or primary group of the object. The caller must have WRITE_DAC access to the object to change the discretionary ACL. The caller must have ACCESS_SYSTEM_SECURITY access to an object to assign a system ACL to the object. This API is modelled after the NtSetSecurityObject() system service. Parameters: ObjectHandle - A handle to an existing object. SecurityInformation - Indicates which security information is to be applied to the object. The value(s) to be assigned are passed in the SecurityDescriptor parameter. SecurityDescriptor - A pointer to a well formed self-relative Security Descriptor and corresponding length. Return Values: STATUS_SUCCESS - normal, successful completion. STATUS_ACCESS_DENIED - The specified handle was not opened for either WRITE_OWNER, WRITE_DAC, or ACCESS_SYSTEM_SECURITY access. STATUS_INVALID_HANDLE - The specified handle is not that of an opened SAM object. STATUS_BAD_DESCRIPTOR_FORMAT - Indicates something about security descriptor is not valid. This may indicate that the structure of the descriptor is not valid or that a component of the descriptor specified via the SecurityInformation parameter is not present in the security descriptor. STATUS_INVALID_PARAMETER - Indicates no security information was specified. STATUS_LAST_ADMIN - Indicates the new SD could potentially lead to the administrator account being unusable and therefore the new protection is being rejected. --*/ { NTSTATUS NtStatus, IgnoreStatus, TmpStatus; PSAMP_OBJECT Context; SAMP_OBJECT_TYPE FoundType; SECURITY_DB_OBJECT_TYPE SecurityDbObjectType; ACCESS_MASK DesiredAccess; PSECURITY_DESCRIPTOR RetrieveSD, SetSD; PISECURITY_DESCRIPTOR PassedSD; ULONG RetrieveSDLength; ULONG ObjectRid; ULONG SecurityDescriptorIndex; HANDLE ClientToken; BOOLEAN NotificationType = TRUE; // // Make sure we understand what RPC is doing for (to) us. // if (SecurityDescriptor == NULL) { return(STATUS_BAD_DESCRIPTOR_FORMAT); } if (SecurityDescriptor->SecurityDescriptor == NULL) { return(STATUS_BAD_DESCRIPTOR_FORMAT); } PassedSD = (PISECURITY_DESCRIPTOR)(SecurityDescriptor->SecurityDescriptor); // // Validate the passed security descriptor // NtStatus = SampValidatePassedSD( SecurityDescriptor->Length, PassedSD ); if (!NT_SUCCESS(NtStatus)) { return(NtStatus); } // // Set the desired access based upon the specified SecurityInformation // DesiredAccess = 0; if ( SecurityInformation & SACL_SECURITY_INFORMATION) { DesiredAccess |= ACCESS_SYSTEM_SECURITY; } if ( SecurityInformation & (OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION)) { DesiredAccess |= WRITE_OWNER; } if ( SecurityInformation & DACL_SECURITY_INFORMATION ) { DesiredAccess |= WRITE_DAC; } // // If no information was specified, then return invalid parameter. // if (DesiredAccess == 0) { return(STATUS_INVALID_PARAMETER); } // // Make sure the specified fields are present in the provided security descriptor. // You can't screw up an SACL or DACL, but you can screw up an owner or group. // Security descriptors must have owner and group fields. // if ( (SecurityInformation & OWNER_SECURITY_INFORMATION) ) { if (PassedSD->Owner == NULL) { return(STATUS_BAD_DESCRIPTOR_FORMAT); } } if ( (SecurityInformation & GROUP_SECURITY_INFORMATION) ) { if (PassedSD->Group == NULL) { return(STATUS_BAD_DESCRIPTOR_FORMAT); } } // // See if the handle is valid and opened for the requested access // NtStatus = SampAcquireWriteLock(); if (!NT_SUCCESS(NtStatus)) { return(NtStatus); } Context = (PSAMP_OBJECT)ObjectHandle; NtStatus = SampLookupContext( Context, DesiredAccess, SampUnknownObjectType, // ExpectedType &FoundType ); switch ( FoundType ) { case SampServerObjectType: { SecurityDescriptorIndex = SAMP_SERVER_SECURITY_DESCRIPTOR; ObjectRid = 0L; NotificationType = FALSE; break; } case SampDomainObjectType: { // // BUGBUG Bug #4388 - allow account operators to access pre-340 // databases // // This fix should NEVER be checked in. It is for one-time // internal use only. // // Pre-340 databases had security descriptors on the domain // objects that didn't allow account operators to operate on // accounts. Replicating one of these old databases to a // newer release copies the bad security descriptors. To avoid // this, do the following ONE TIME only: // // Uncomment the code immediately below. Build a new // SAMSRV.DLL. Put it on a BDC running the target version // of the system. Replicate the pre-340 database from the // PDC (which may be running a more recent version of the // system, but still has the bad pre-340 descriptors from // earlier replications). // // That's it; you've got a fixed database that can be safely // replicated in the future. Get rid of this temporary code. // (And put the real SAMSRV.DLL back on the BDC). // // IgnoreStatus = SampDeReferenceContext( Context, FALSE ); // IgnoreStatus = SampReleaseWriteLock( FALSE ); // return( STATUS_SUCCESS ); // SecurityDbObjectType = SecurityDbObjectSamDomain; SecurityDescriptorIndex = SAMP_DOMAIN_SECURITY_DESCRIPTOR; ObjectRid = 0L; break; } case SampUserObjectType: { SecurityDbObjectType = SecurityDbObjectSamUser; SecurityDescriptorIndex = SAMP_USER_SECURITY_DESCRIPTOR; ObjectRid = Context->TypeBody.User.Rid; break; } case SampGroupObjectType: { SecurityDbObjectType = SecurityDbObjectSamGroup; SecurityDescriptorIndex = SAMP_GROUP_SECURITY_DESCRIPTOR; ObjectRid = Context->TypeBody.Group.Rid; break; } case SampAliasObjectType: { SecurityDbObjectType = SecurityDbObjectSamAlias; SecurityDescriptorIndex = SAMP_ALIAS_SECURITY_DESCRIPTOR; ObjectRid = Context->TypeBody.Alias.Rid; break; } default: { NotificationType = FALSE; } } if (NT_SUCCESS(NtStatus)) { // // Get the security descriptor // RetrieveSD = NULL; RetrieveSDLength = 0; NtStatus = SampGetObjectSD( Context, &RetrieveSDLength, &RetrieveSD); // // Make sure the descriptor does not break any Administrator // restrictions. // NtStatus = SampCheckForDescriptorRestrictions( Context, FoundType, ObjectRid, PassedSD ); if (NT_SUCCESS(NtStatus)) { // // copy the retrieved descriptor into process heap so we can use RTL routines. // SetSD = NULL; if (NT_SUCCESS(NtStatus)) { SetSD = RtlAllocateHeap( RtlProcessHeap(), 0, RetrieveSDLength ); if ( SetSD == NULL) { NtStatus = STATUS_INSUFFICIENT_RESOURCES; } else { RtlCopyMemory( SetSD, RetrieveSD, RetrieveSDLength ); } } if (NT_SUCCESS(NtStatus)) { // // if the caller is replacing the owner and he is not // trusted, then a handle to the impersonation token is // necessary. If the caller is trusted then take process // token. // ClientToken = 0; if ( (SecurityInformation & OWNER_SECURITY_INFORMATION) ) { if(!Context->TrustedClient) { NtStatus = I_RpcMapWin32Status(RpcImpersonateClient( NULL )); if (NT_SUCCESS(NtStatus)) { NtStatus = NtOpenThreadToken( NtCurrentThread(), TOKEN_QUERY, TRUE, //OpenAsSelf &ClientToken ); ASSERT( (ClientToken == 0) || NT_SUCCESS(NtStatus) ); // // Stop impersonating the client // IgnoreStatus = I_RpcMapWin32Status(RpcRevertToSelf()); ASSERT( NT_SUCCESS(IgnoreStatus) ); } } else { // // trusted client // NtStatus = NtOpenProcessToken( NtCurrentProcess(), TOKEN_QUERY, &ClientToken ); ASSERT( (ClientToken == 0) || NT_SUCCESS(NtStatus) ); } } if (NT_SUCCESS(NtStatus)) { // // Build the replacement security descriptor. // This must be done in process heap to satisfy the needs of the RTL // routine. // NtStatus = RtlSetSecurityObject( SecurityInformation, PassedSD, &SetSD, &SampObjectInformation[FoundType].GenericMapping, ClientToken ); if (ClientToken != 0) { IgnoreStatus = NtClose( ClientToken ); ASSERT(NT_SUCCESS(IgnoreStatus)); } if (NT_SUCCESS(NtStatus)) { // // Apply the security descriptor back onto the object. // NtStatus = SampSetAccessAttribute( Context, SecurityDescriptorIndex, SetSD, RtlLengthSecurityDescriptor(SetSD) ); } } } // // Free up allocated memory // if (RetrieveSD != NULL) { MIDL_user_free( RetrieveSD ); } if (SetSD != NULL) { RtlFreeHeap( RtlProcessHeap(), 0, SetSD ); } // // De-reference the object // if ( NT_SUCCESS( NtStatus ) ) { NtStatus = SampDeReferenceContext( Context, TRUE ); } else { IgnoreStatus = SampDeReferenceContext( Context, FALSE ); } } } //end_if // // Commit the changes to disk. // if ( NT_SUCCESS( NtStatus ) ) { NtStatus = SampCommitAndRetainWriteLock(); if ( NotificationType && NT_SUCCESS( NtStatus ) ) { SampNotifyNetlogonOfDelta( SecurityDbChange, SecurityDbObjectType, ObjectRid, (PUNICODE_STRING) NULL, (DWORD) FALSE, // Replicate immediately NULL // Delta data ); } } // // Release lock and propagate errors // TmpStatus = SampReleaseWriteLock( FALSE ); if (NT_SUCCESS(NtStatus)) { NtStatus = TmpStatus; } return(NtStatus); } NTSTATUS SampValidatePassedSD( IN ULONG Length, IN PISECURITY_DESCRIPTOR PassedSD ) /*++ Routine Description: This routine validates that a passed security descriptor is valid and does not extend beyond its expressed length. Parameters: Length - The length of the security descriptor. This should be what RPC used to allocate memory to receive the security descriptor. PassedSD - Points to the security descriptor to inspect. Return Values: STATUS_SUCCESS - The security descriptor is valid. STATUS_BAD_DESCRIPTOR_FORMAT - Something was wrong with the security descriptor. It might have extended beyond its limits or had an invalid component. --*/ { NTSTATUS NtStatus; PACL Acl; PSID Sid; PUCHAR SDEnd; BOOLEAN Present, IgnoreBoolean; if (Length < SECURITY_DESCRIPTOR_MIN_LENGTH) { return(STATUS_BAD_DESCRIPTOR_FORMAT); } SDEnd = (PUCHAR)PassedSD + Length; try { // // Make sure the DACL is within the SD // NtStatus = RtlGetDaclSecurityDescriptor( (PSECURITY_DESCRIPTOR)PassedSD, &Present, &Acl, &IgnoreBoolean ); if (!NT_SUCCESS(NtStatus)) { return( STATUS_BAD_DESCRIPTOR_FORMAT ); } if (Present) { if (Acl != NULL) { // // Make sure the ACl header is in the buffer. // if ( (((PUCHAR)Acl)+sizeof(ACL) > SDEnd) || (((PUCHAR)Acl) < (PUCHAR)PassedSD) ) { return( STATUS_BAD_DESCRIPTOR_FORMAT ); } // // Make sure the rest of the ACL is within the buffer // if ( ((PUCHAR)Acl)+Acl->AclSize > SDEnd) { return( STATUS_BAD_DESCRIPTOR_FORMAT ); } // // Make sure the rest of the ACL is valid // if (!RtlValidAcl( Acl )) { return( STATUS_BAD_DESCRIPTOR_FORMAT ); } } } // // Make sure the SACL is within the SD // NtStatus = RtlGetSaclSecurityDescriptor( (PSECURITY_DESCRIPTOR)PassedSD, &Present, &Acl, &IgnoreBoolean ); if (!NT_SUCCESS(NtStatus)) { return( STATUS_BAD_DESCRIPTOR_FORMAT ); } if (Present) { if (Acl != NULL) { // // Make sure the ACl header is in the buffer. // if ( (((PUCHAR)Acl)+sizeof(ACL) > SDEnd) || (((PUCHAR)Acl) < (PUCHAR)PassedSD) ) { return( STATUS_BAD_DESCRIPTOR_FORMAT ); } // // Make sure the rest of the ACL is within the buffer // if ( ((PUCHAR)Acl)+Acl->AclSize > SDEnd) { return( STATUS_BAD_DESCRIPTOR_FORMAT ); } // // Make sure the rest of the ACL is valid // if (!RtlValidAcl( Acl )) { return( STATUS_BAD_DESCRIPTOR_FORMAT ); } } } // // Make sure the Owner SID is within the SD // NtStatus = RtlGetOwnerSecurityDescriptor( (PSECURITY_DESCRIPTOR)PassedSD, &Sid, &IgnoreBoolean ); if (!NT_SUCCESS(NtStatus)) { return( STATUS_BAD_DESCRIPTOR_FORMAT ); } if (Sid != NULL) { // // Make sure the SID header is in the SD // if ( (((PUCHAR)Sid)+sizeof(SID)-(ANYSIZE_ARRAY*sizeof(ULONG)) > SDEnd) || (((PUCHAR)Sid) < (PUCHAR)PassedSD) ) { return( STATUS_BAD_DESCRIPTOR_FORMAT ); } // // Make sure there aren't too many sub-authorities // if (((PISID)Sid)->SubAuthorityCount > SID_MAX_SUB_AUTHORITIES) { return( STATUS_BAD_DESCRIPTOR_FORMAT ); } // // Make sure the rest of the SID is within the SD // if ( ((PUCHAR)Sid)+RtlLengthSid(Sid) > SDEnd) { return( STATUS_BAD_DESCRIPTOR_FORMAT ); } } // // Make sure the Group SID is within the SD // NtStatus = RtlGetGroupSecurityDescriptor( (PSECURITY_DESCRIPTOR)PassedSD, &Sid, &IgnoreBoolean ); if (!NT_SUCCESS(NtStatus)) { return( STATUS_BAD_DESCRIPTOR_FORMAT ); } if (Sid != NULL) { // // Make sure the SID header is in the SD // if ( (((PUCHAR)Sid)+sizeof(SID)-(ANYSIZE_ARRAY*sizeof(ULONG)) > SDEnd) || (((PUCHAR)Sid) < (PUCHAR)PassedSD) ) { return( STATUS_BAD_DESCRIPTOR_FORMAT ); } // // Make sure there aren't too many sub-authorities // if (((PISID)Sid)->SubAuthorityCount > SID_MAX_SUB_AUTHORITIES) { return( STATUS_BAD_DESCRIPTOR_FORMAT ); } // // Make sure the rest of the SID is within the SD // if ( ((PUCHAR)Sid)+RtlLengthSid(Sid) > SDEnd) { return( STATUS_BAD_DESCRIPTOR_FORMAT ); } } } except(EXCEPTION_EXECUTE_HANDLER) { return( STATUS_BAD_DESCRIPTOR_FORMAT ); } // end_try return(STATUS_SUCCESS); } NTSTATUS SampCheckForDescriptorRestrictions( IN PSAMP_OBJECT Context, IN SAMP_OBJECT_TYPE ObjectType, IN ULONG ObjectRid, IN PISECURITY_DESCRIPTOR PassedSD ) /*++ Routine Description: This function ensures that the passed security descriptor, which is being applied to an object of type 'FoundType' with a Rid of value 'ObjectRid', does not violate any policies. For example, you can not set protection on the Administrator user account such that the administrator is unable to change her password. Parameters: Context - The caller's context. This is used to determine whether the caller is trusted or not. If the caller is trusted, then there are no restrictions. ObjectType - The type of object the new security descriptor is being applied to. ObjectRid - The RID of the object the new security descriptor is being applied to. PassedSD - The security descriptor passed by the client. Return Values: STATUS_SUCCESS - normal, successful completion. STATUS_LAST_ADMIN - Indicates the new SD could potentially lead to the administrator account being unusable and therefore the new protection is being rejected. --*/ { NTSTATUS NtStatus; BOOLEAN DaclPresent, AdminSid, Done, IgnoreBoolean; PACL Dacl; ACL_SIZE_INFORMATION DaclInfo; PACCESS_ALLOWED_ACE Ace; ACCESS_MASK Accesses, Remaining; ULONG AceIndex; GENERIC_MAPPING UserMap = {USER_READ, USER_WRITE, USER_EXECUTE, USER_ALL_ACCESS}; // // No checking for trusted client operations // if (Context->TrustedClient) { return(STATUS_SUCCESS); } NtStatus = RtlGetDaclSecurityDescriptor ( (PSECURITY_DESCRIPTOR)PassedSD, &DaclPresent, &Dacl, &IgnoreBoolean //DaclDefaulted ); ASSERT(NT_SUCCESS(NtStatus)); if (!DaclPresent) { // // Not replacing the DACL // return(STATUS_SUCCESS); } if (Dacl == NULL) { // // Assigning "World all access" // return(STATUS_SUCCESS); } if (!RtlValidAcl(Dacl)) { return(STATUS_INVALID_ACL); } NtStatus = RtlQueryInformationAcl ( Dacl, &DaclInfo, sizeof(ACL_SIZE_INFORMATION), AclSizeInformation ); ASSERT(NT_SUCCESS(NtStatus)); // // Enforce Administrator user policies // NtStatus = STATUS_SUCCESS; if (ObjectRid == DOMAIN_USER_RID_ADMIN) { ASSERT(ObjectType == SampUserObjectType); // // For the administrator account, the ACL must grant // these accesses: // Remaining = USER_READ_GENERAL | USER_READ_PREFERENCES | USER_WRITE_PREFERENCES | USER_READ_LOGON | USER_READ_ACCOUNT | USER_WRITE_ACCOUNT | USER_CHANGE_PASSWORD | USER_FORCE_PASSWORD_CHANGE | USER_LIST_GROUPS | USER_READ_GROUP_INFORMATION | USER_WRITE_GROUP_INFORMATION; // // to these SIDs: // // \Administrator // \Administrators // // It doesn't matter which accesses are granted to which SIDs, // as long as collectively all the accesses are granted. // // // Walk the ACEs collecting accesses that are granted to these // SIDs. Make sure there are no DENYs that prevent them from // being granted. // Done = FALSE; for ( AceIndex=0; (AceIndex < DaclInfo.AceCount) && !Done; AceIndex++) { NtStatus = RtlGetAce ( Dacl, AceIndex, &((PVOID)Ace) ); // // Don't do anything with inherit-only ACEs // if ((Ace->Header.AceFlags & INHERIT_ONLY_ACE) == 0) { // // Note that we expect ACCESS_ALLOWED_ACE and ACCESS_DENIED_ACE // to be identical structures in the following switch statement. // switch (Ace->Header.AceType) { case ACCESS_ALLOWED_ACE_TYPE: case ACCESS_DENIED_ACE_TYPE: { // // Is this an interesting SID // AdminSid = RtlEqualSid( ((PSID)(&Ace->SidStart)), SampAdministratorUserSid) || RtlEqualSid( ((PSID)(&Ace->SidStart)), SampAdministratorsAliasSid); if (AdminSid) { // // Map the accesses granted or denied // Accesses = Ace->Mask; RtlMapGenericMask( &Accesses, &UserMap ); if (Ace->Header.AceType == ACCESS_ALLOWED_ACE_TYPE) { Remaining &= ~Accesses; if (Remaining == 0) { // // All necessary accesses granted // Done = TRUE; } } else { ASSERT(Ace->Header.AceType == ACCESS_DENIED_ACE_TYPE); if (Remaining & Accesses) { // // We've just been denied some necessary // accesses that haven't yet been granted. // Done = TRUE; } } } break; } default: break; } // end_switch if (Done) { break; } } } // end_for if (Remaining != 0) { NtStatus = STATUS_LAST_ADMIN; } } // end_if (Administrator Account) return(NtStatus); } NTSTATUS SamrQuerySecurityObject( IN SAMPR_HANDLE ObjectHandle, IN SECURITY_INFORMATION SecurityInformation, OUT PSAMPR_SR_SECURITY_DESCRIPTOR *SecurityDescriptor ) /*++ Routine Description: This function (SamrQuerySecurityObject) returns to the caller requested security information currently assigned to an object. Based on the caller's access rights this procedure will return a security descriptor containing any or all of the object's owner ID, group ID, discretionary ACL or system ACL. To read the owner ID, group ID, or the discretionary ACL the caller must be granted READ_CONTROL access to the object. To read the system ACL the caller must be granted ACCESS_SYSTEM_SECURITY access. This API is modelled after the NtQuerySecurityObject() system service. Parameters: ObjectHandle - A handle to an existing object. SecurityInformation - Supplies a value describing which pieces of security information are being queried. SecurityDescriptor - Provides a pointer to a structure to be filled in with a security descriptor containing the requested security information. This information is returned in the form of a self-relative security descriptor. Return Values: STATUS_SUCCESS - normal, successful completion. STATUS_ACCESS_DENIED - The specified handle was not opened for either READ_CONTROL or ACCESS_SYSTEM_SECURITY access. STATUS_INVALID_HANDLE - The specified handle is not that of an opened SAM object. --*/ { NTSTATUS NtStatus, IgnoreStatus; PSAMP_OBJECT Context; SAMP_OBJECT_TYPE FoundType; ACCESS_MASK DesiredAccess; PSAMPR_SR_SECURITY_DESCRIPTOR RpcSD; PSECURITY_DESCRIPTOR RetrieveSD, ReturnSD; ULONG RetrieveSDLength, ReturnSDLength; ReturnSD = NULL; // // Make sure we understand what RPC is doing for (to) us. // ASSERT (*SecurityDescriptor == NULL); // // Set the desired access based upon the requested SecurityInformation // DesiredAccess = 0; if ( SecurityInformation & SACL_SECURITY_INFORMATION) { DesiredAccess |= ACCESS_SYSTEM_SECURITY; } if ( SecurityInformation & (DACL_SECURITY_INFORMATION | OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION) ) { DesiredAccess |= READ_CONTROL; } // // Allocate the first block of returned memory // RpcSD = MIDL_user_allocate( sizeof(SAMPR_SR_SECURITY_DESCRIPTOR) ); if (RpcSD == NULL) { return(STATUS_INSUFFICIENT_RESOURCES); } RpcSD->Length = 0; RpcSD->SecurityDescriptor = NULL; // // See if the handle is valid and opened for the requested access // SampAcquireReadLock(); Context = (PSAMP_OBJECT)ObjectHandle; NtStatus = SampLookupContext( Context, DesiredAccess, SampUnknownObjectType, // ExpectedType &FoundType ); if (NT_SUCCESS(NtStatus)) { // // Get the security descriptor // RetrieveSDLength = 0; NtStatus = SampGetObjectSD( Context, &RetrieveSDLength, &RetrieveSD); if (NT_SUCCESS(NtStatus)) { // // blank out the parts that aren't to be returned // if ( !(SecurityInformation & SACL_SECURITY_INFORMATION) ) { ((PISECURITY_DESCRIPTOR)RetrieveSD)->Control &= ~SE_SACL_PRESENT; } if ( !(SecurityInformation & DACL_SECURITY_INFORMATION) ) { ((PISECURITY_DESCRIPTOR)RetrieveSD)->Control &= ~SE_DACL_PRESENT; } if ( !(SecurityInformation & OWNER_SECURITY_INFORMATION) ) { ((PISECURITY_DESCRIPTOR)RetrieveSD)->Owner = NULL; } if ( !(SecurityInformation & GROUP_SECURITY_INFORMATION) ) { ((PISECURITY_DESCRIPTOR)RetrieveSD)->Group = NULL; } // // Determine how much memory is needed for a self-relative // security descriptor containing just this information. // ReturnSDLength = 0; NtStatus = RtlMakeSelfRelativeSD( RetrieveSD, NULL, &ReturnSDLength ); ASSERT(!NT_SUCCESS(NtStatus)); if (NtStatus == STATUS_BUFFER_TOO_SMALL) { ReturnSD = MIDL_user_allocate( ReturnSDLength ); if (ReturnSD == NULL) { NtStatus = STATUS_INSUFFICIENT_RESOURCES; } else { // // make an appropriate self-relative security descriptor // NtStatus = RtlMakeSelfRelativeSD( RetrieveSD, ReturnSD, &ReturnSDLength ); } } // // Free up the retrieved SD // MIDL_user_free( RetrieveSD ); } // // De-reference the object // IgnoreStatus = SampDeReferenceContext( Context, FALSE ); } // // Free the read lock // SampReleaseReadLock(); // // If we succeeded, set up the return buffer. // Otherwise, free any allocated memory. // if (NT_SUCCESS(NtStatus)) { RpcSD->Length = ReturnSDLength; RpcSD->SecurityDescriptor = (PUCHAR)ReturnSD; (*SecurityDescriptor) = RpcSD; } else { MIDL_user_free( RpcSD ); if (ReturnSD != NULL) { MIDL_user_free(ReturnSD); } (*SecurityDescriptor) = NULL; } return(NtStatus); }