/* * @implemented */ BOOLEAN NTAPI RtlValidSecurityDescriptor(IN PSECURITY_DESCRIPTOR SecurityDescriptor) { PISECURITY_DESCRIPTOR Sd = (PISECURITY_DESCRIPTOR)SecurityDescriptor; PSID Owner, Group; PACL Sacl, Dacl; PAGED_CODE_RTL(); _SEH2_TRY { /* Fail on bad revisions */ if (Sd->Revision != SECURITY_DESCRIPTOR_REVISION) return FALSE; /* Owner SID must be valid if present */ Owner = SepGetOwnerFromDescriptor(Sd); if ((Owner) && (!RtlValidSid(Owner))) return FALSE; /* Group SID must be valid if present */ Group = SepGetGroupFromDescriptor(Sd); if ((Owner) && (!RtlValidSid(Group))) return FALSE; /* DACL must be valid if present */ Dacl = SepGetDaclFromDescriptor(Sd); if ((Dacl) && (!RtlValidAcl(Dacl))) return FALSE; /* SACL must be valid if present */ Sacl = SepGetSaclFromDescriptor(Sd); if ((Sacl) && (!RtlValidAcl(Sacl))) return FALSE; } _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { /* Access fault, bail out */ return FALSE; } _SEH2_END; /* All good */ return TRUE; }
/************************************************************************** * RtlFirstFreeAce [NTDLL.@] * looks for the AceCount+1 ACE, and if it is still within the alloced * ACL, return a pointer to it */ BOOLEAN WINAPI RtlFirstFreeAce( PACL acl, PACE_HEADER* x) { PACE_HEADER ace; int i; *x = 0; if(!RtlValidAcl(acl)) return FALSE; ace = (PACE_HEADER)(acl+1); for (i=0;i<acl->AceCount;i++) { if ((DWORD)ace>=(((DWORD)acl)+acl->AclSize)) break; ace = (PACE_HEADER)(((BYTE*)ace)+ace->AceSize); } /* x should be non null if ACL is valid but there isn't a free ace */ *x = ace; if ((DWORD)ace>=(((DWORD)acl)+acl->AclSize)) return FALSE; return TRUE; }
/* * @implemented */ BOOLEAN NTAPI RtlValidRelativeSecurityDescriptor(IN PSECURITY_DESCRIPTOR SecurityDescriptorInput, IN ULONG SecurityDescriptorLength, IN SECURITY_INFORMATION RequiredInformation) { PISECURITY_DESCRIPTOR_RELATIVE Sd = (PISECURITY_DESCRIPTOR_RELATIVE)SecurityDescriptorInput; PSID Owner, Group; PACL Dacl, Sacl; ULONG Length; PAGED_CODE_RTL(); /* Note that Windows allows no DACL/SACL even if RequiredInfo wants it */ /* Do we have enough space, is the revision vaild, and is this SD relative? */ if ((SecurityDescriptorLength < sizeof(SECURITY_DESCRIPTOR_RELATIVE)) || (Sd->Revision != SECURITY_DESCRIPTOR_REVISION) || !(Sd->Control & SE_SELF_RELATIVE)) { /* Nope, bail out */ return FALSE; } /* Is there an owner? */ if (Sd->Owner) { /* Try to access it */ if (!RtlpValidateSDOffsetAndSize(Sd->Owner, SecurityDescriptorLength, sizeof(SID), &Length)) { /* It's beyond the buffer, fail */ return FALSE; } /* Read the owner, check if it's valid and if the buffer contains it */ Owner = (PSID)((ULONG_PTR)Sd->Owner + (ULONG_PTR)Sd); if (!RtlValidSid(Owner) || (Length < RtlLengthSid(Owner))) return FALSE; } else if (RequiredInformation & OWNER_SECURITY_INFORMATION) { /* No owner but the caller expects one, fail */ return FALSE; } /* Is there a group? */ if (Sd->Group) { /* Try to access it */ if (!RtlpValidateSDOffsetAndSize(Sd->Group, SecurityDescriptorLength, sizeof(SID), &Length)) { /* It's beyond the buffer, fail */ return FALSE; } /* Read the group, check if it's valid and if the buffer contains it */ Group = (PSID)((ULONG_PTR)Sd->Group + (ULONG_PTR)Sd); if (!RtlValidSid(Group) || (Length < RtlLengthSid(Group))) return FALSE; } else if (RequiredInformation & GROUP_SECURITY_INFORMATION) { /* No group, but the caller expects one, fail */ return FALSE; } /* Is there a DACL? */ if ((Sd->Control & SE_DACL_PRESENT) == SE_DACL_PRESENT) { /* Try to access it */ if (!RtlpValidateSDOffsetAndSize(Sd->Dacl, SecurityDescriptorLength, sizeof(ACL), &Length)) { /* It's beyond the buffer, fail */ return FALSE; } /* Read the DACL, check if it's valid and if the buffer contains it */ Dacl = (PSID)((ULONG_PTR)Sd->Dacl + (ULONG_PTR)Sd); if (!(RtlValidAcl(Dacl)) || (Length < Dacl->AclSize)) return FALSE; } /* Is there a SACL? */ if ((Sd->Control & SE_SACL_PRESENT) == SE_SACL_PRESENT) { /* Try to access it */ if (!RtlpValidateSDOffsetAndSize(Sd->Sacl, SecurityDescriptorLength, sizeof(ACL), &Length)) { /* It's beyond the buffer, fail */ return FALSE; } /* Read the SACL, check if it's valid and if the buffer contains it */ Sacl = (PSID)((ULONG_PTR)Sd->Sacl + (ULONG_PTR)Sd); if (!(RtlValidAcl(Sacl)) || (Length < Sacl->AclSize)) return FALSE; } /* All good */ return TRUE; }
NTSTATUS NTAPI RtlpAddKnownAce(IN PACL Acl, IN ULONG Revision, IN ULONG Flags, IN ACCESS_MASK AccessMask, IN PSID Sid, IN UCHAR Type) { PKNOWN_ACE Ace; ULONG AceSize, InvalidFlags; PAGED_CODE_RTL(); /* Check the validity of the SID */ if (!RtlValidSid(Sid)) return STATUS_INVALID_SID; /* Check the validity of the revision */ if ((Acl->AclRevision > ACL_REVISION4) || (Revision > ACL_REVISION4)) { return STATUS_REVISION_MISMATCH; } /* Pick the smallest of the revisions */ if (Revision < Acl->AclRevision) Revision = Acl->AclRevision; /* Validate the flags */ if (Type == SYSTEM_AUDIT_ACE_TYPE) { InvalidFlags = Flags & ~(VALID_INHERIT_FLAGS | SUCCESSFUL_ACCESS_ACE_FLAG | FAILED_ACCESS_ACE_FLAG); } else { InvalidFlags = Flags & ~VALID_INHERIT_FLAGS; } /* If flags are invalid, bail out */ if (InvalidFlags != 0) return STATUS_INVALID_PARAMETER; /* If ACL is invalid, bail out */ if (!RtlValidAcl(Acl)) return STATUS_INVALID_ACL; /* If there's no free ACE, bail out */ if (!RtlFirstFreeAce(Acl, (PACE*)&Ace)) return STATUS_INVALID_ACL; /* Calculate the size of the ACE and bail out if it's too small */ AceSize = RtlLengthSid(Sid) + sizeof(ACE); if (!(Ace) || ((ULONG_PTR)Ace + AceSize > (ULONG_PTR)Acl + Acl->AclSize)) { return STATUS_ALLOTTED_SPACE_EXCEEDED; } /* Initialize the header and common fields */ Ace->Header.AceFlags = (BYTE)Flags; Ace->Header.AceType = Type; Ace->Header.AceSize = (WORD)AceSize; Ace->Mask = AccessMask; /* Copy the SID */ RtlCopySid(RtlLengthSid(Sid), &Ace->SidStart, Sid); /* Fill out the ACL header and return */ Acl->AceCount++; Acl->AclRevision = (BYTE)Revision; return STATUS_SUCCESS; }
BOOLEAN RtlAccessCheckEx( IN PSECURITY_DESCRIPTOR_ABSOLUTE SecurityDescriptor, IN PACCESS_TOKEN AccessToken, IN ACCESS_MASK DesiredAccess, IN ACCESS_MASK PreviouslyGrantedAccess, IN PGENERIC_MAPPING GenericMapping, OUT PACCESS_MASK RemainingDesiredAccess, OUT PACCESS_MASK GrantedAccess, OUT PNTSTATUS AccessStatus ) { NTSTATUS status = STATUS_ACCESS_DENIED; BOOLEAN isLocked = FALSE; ACCESS_MASK grantedAccess = PreviouslyGrantedAccess; ACCESS_MASK deniedAccess = 0; ACCESS_MASK desiredAccess = DesiredAccess; BOOLEAN wantMaxAllowed = FALSE; USHORT aclSizeUsed = 0; USHORT aceOffset = 0; PACE_HEADER aceHeader = NULL; union { SID Sid; BYTE Buffer[SID_MAX_SIZE]; } sidBuffer; ULONG ulSidSize = sizeof(sidBuffer); if (!SecurityDescriptor || !AccessToken || !GenericMapping) { status = STATUS_INVALID_PARAMETER; GOTO_CLEANUP(); } if (!LW_IS_VALID_FLAGS(DesiredAccess, VALID_DESIRED_ACCESS_MASK)) { status = STATUS_INVALID_PARAMETER; GOTO_CLEANUP(); } if (!LW_IS_VALID_FLAGS(PreviouslyGrantedAccess, VALID_GRANTED_ACCESS_MASK)) { status = STATUS_INVALID_PARAMETER; GOTO_CLEANUP(); } if ((SecurityDescriptor->Owner == NULL) || (SecurityDescriptor->Group == NULL)) { status = STATUS_INVALID_SECURITY_DESCR; GOTO_CLEANUP(); } wantMaxAllowed = IsSetFlag(desiredAccess, MAXIMUM_ALLOWED); ClearFlag(desiredAccess, MAXIMUM_ALLOWED); RtlMapGenericMask(&desiredAccess, GenericMapping); // // NT AUTHORITY\SYSTEM is always allowed an access // status = RtlCreateWellKnownSid(WinLocalSystemSid, NULL, &sidBuffer.Sid, &ulSidSize); GOTO_CLEANUP_ON_STATUS(status); SHARED_LOCK_RWLOCK(&AccessToken->RwLock, isLocked); if (RtlIsSidMemberOfToken(AccessToken, &sidBuffer.Sid)) { if (wantMaxAllowed) { SetFlag(desiredAccess, STANDARD_RIGHTS_ALL); SetFlag(desiredAccess, GENERIC_ALL); RtlMapGenericMask(&desiredAccess, GenericMapping); } SetFlag(grantedAccess, desiredAccess); desiredAccess = 0; status = STATUS_SUCCESS; GOTO_CLEANUP(); } if (wantMaxAllowed || IsSetFlag(desiredAccess, ACCESS_SYSTEM_SECURITY)) { // TODO-Handle ACCESS_SYSTEM_SECURITY by checking SE_SECURITY_NAME // privilege. For now, requesting ACCESS_SYSTEM_SECURITY is not // allowed. ulSidSize = sizeof(sidBuffer); status = RtlCreateWellKnownSid( WinBuiltinAdministratorsSid, NULL, &sidBuffer.Sid, &ulSidSize); GOTO_CLEANUP_ON_STATUS(status); if (RtlIsSidMemberOfToken(AccessToken, &sidBuffer.Sid)) { SetFlag(grantedAccess, ACCESS_SYSTEM_SECURITY); ClearFlag(desiredAccess, ACCESS_SYSTEM_SECURITY); } else if (IsSetFlag(desiredAccess, ACCESS_SYSTEM_SECURITY)) { status = STATUS_PRIVILEGE_NOT_HELD; GOTO_CLEANUP(); } } if (wantMaxAllowed || IsSetFlag(desiredAccess, WRITE_OWNER)) { // TODO-Allow WRITE_OWNER if have SE_TAKE_OWNERSHIP_NAME regardless // of DACL. // // BUILTIN\Administrators are always allowed WRITE_OWNER // ulSidSize = sizeof(sidBuffer); status = RtlCreateWellKnownSid( WinBuiltinAdministratorsSid, NULL, &sidBuffer.Sid, &ulSidSize); GOTO_CLEANUP_ON_STATUS(status); if (RtlIsSidMemberOfToken(AccessToken, &sidBuffer.Sid)) { SetFlag(grantedAccess, WRITE_OWNER); ClearFlag(desiredAccess, WRITE_OWNER); } } // // Owner can always read the SD and write the DACL. // if (wantMaxAllowed || IsSetFlag(desiredAccess, READ_CONTROL | WRITE_DAC)) { if (RtlIsSidMemberOfToken(AccessToken, SecurityDescriptor->Owner)) { if (wantMaxAllowed) { desiredAccess |= (READ_CONTROL | WRITE_DAC); } SetFlag(grantedAccess, (READ_CONTROL | WRITE_DAC) & desiredAccess); ClearFlag(desiredAccess, grantedAccess); } } // TODO-MAXIMUM_ALLOWED wrt privileges and WRITE_OWNER and // ACCESS_SYSTEM_SECURITY above. if (!SecurityDescriptor->Dacl) { // TODO-Interplay with special bits above? if (wantMaxAllowed) { SetFlag(desiredAccess, STANDARD_RIGHTS_ALL); SetFlag(desiredAccess, GENERIC_ALL); RtlMapGenericMask(&desiredAccess, GenericMapping); } SetFlag(grantedAccess, desiredAccess); desiredAccess = 0; status = STATUS_SUCCESS; GOTO_CLEANUP(); } if (!RtlValidAcl(SecurityDescriptor->Dacl, &aclSizeUsed)) { status = STATUS_INVALID_ACL; GOTO_CLEANUP(); } while (wantMaxAllowed || desiredAccess) { status = RtlIterateAce(SecurityDescriptor->Dacl, aclSizeUsed, &aceOffset, &aceHeader); if (STATUS_NO_MORE_ENTRIES == status) { break; } GOTO_CLEANUP_ON_STATUS(status); // Check ACE switch (aceHeader->AceType) { case ACCESS_ALLOWED_ACE_TYPE: { PACCESS_ALLOWED_ACE ace = (PACCESS_ALLOWED_ACE) aceHeader; ACCESS_MASK mask = ace->Mask; RtlMapGenericMask(&mask, GenericMapping); if (wantMaxAllowed || IsSetFlag(desiredAccess, mask)) { // SID in token => add bits to granted bits PSID sid = RtlpGetSidAccessAllowedAce(ace); if (RtlIsSidMemberOfToken(AccessToken, sid)) { if (wantMaxAllowed) { SetFlag(grantedAccess, mask & ~deniedAccess); } else { SetFlag(grantedAccess, mask & desiredAccess); } ClearFlag(desiredAccess, grantedAccess); } } break; } case ACCESS_DENIED_ACE_TYPE: { // Allowed and deny ACEs are isomorphic. PACCESS_ALLOWED_ACE ace = (PACCESS_ALLOWED_ACE) aceHeader; ACCESS_MASK mask = ace->Mask; RtlMapGenericMask(&mask, GenericMapping); if (wantMaxAllowed || IsSetFlag(desiredAccess, mask)) { // SID in token => exit with STATUS_ACCESS_DENIED PSID sid = RtlpGetSidAccessAllowedAce(ace); if (RtlIsSidMemberOfToken(AccessToken, sid)) { if (wantMaxAllowed) { SetFlag(deniedAccess, mask); } else { status = STATUS_ACCESS_DENIED; GOTO_CLEANUP(); } ClearFlag(desiredAccess, deniedAccess); } } break; } default: // ignore break; } } status = desiredAccess ? STATUS_ACCESS_DENIED : STATUS_SUCCESS; cleanup: UNLOCK_RWLOCK(&AccessToken->RwLock, isLocked); if (NT_SUCCESS(status) && !LW_IS_VALID_FLAGS(grantedAccess, VALID_GRANTED_ACCESS_MASK)) { status = STATUS_ASSERTION_FAILURE; } if (!NT_SUCCESS(status)) { grantedAccess = PreviouslyGrantedAccess; } if (RemainingDesiredAccess) { *RemainingDesiredAccess = desiredAccess; } *GrantedAccess = grantedAccess; *AccessStatus = status; return NT_SUCCESS(status) ? TRUE : FALSE; }
NTSTATUS RtlCreateAccessToken( OUT PACCESS_TOKEN* AccessToken, IN PTOKEN_USER User, IN PTOKEN_GROUPS Groups, IN PTOKEN_PRIVILEGES Privileges, IN PTOKEN_OWNER Owner, IN PTOKEN_PRIMARY_GROUP PrimaryGroup, IN PTOKEN_DEFAULT_DACL DefaultDacl, IN OPTIONAL PTOKEN_UNIX Unix ) { NTSTATUS status = STATUS_SUCCESS; int unixError = 0; ULONG requiredSize = 0; PACCESS_TOKEN token = NULL; ULONG i = 0; ULONG size = 0; PVOID location = NULL; if (!User || !User->User.Sid || !Groups || !Owner || !PrimaryGroup || !DefaultDacl) { status = STATUS_INVALID_PARAMETER; GOTO_CLEANUP(); } if (!RtlValidSid(User->User.Sid) || (Owner->Owner && !RtlValidSid(Owner->Owner)) || (PrimaryGroup->PrimaryGroup && !RtlValidSid(PrimaryGroup->PrimaryGroup))) { status = STATUS_INVALID_SID; GOTO_CLEANUP(); } // No user attributes currently exist. if (User->User.Attributes != 0) { status = STATUS_INVALID_PARAMETER; GOTO_CLEANUP(); } for (i = 0; i < Groups->GroupCount; i++) { // TODO-Perhaps validate Group attributes if (!Groups->Groups[i].Sid) { status = STATUS_INVALID_PARAMETER; GOTO_CLEANUP(); } if (!RtlValidSid(Groups->Groups[i].Sid)) { status = STATUS_INVALID_SID; GOTO_CLEANUP(); } } if (DefaultDacl->DefaultDacl && !RtlValidAcl(DefaultDacl->DefaultDacl, NULL)) { status = STATUS_INVALID_ACL; GOTO_CLEANUP(); } // Compute size required requiredSize = sizeof(*token); size = RtlLengthSid(User->User.Sid); status = RtlSafeAddULONG(&requiredSize, requiredSize, size); GOTO_CLEANUP_ON_STATUS(status); if (Owner->Owner) { size = RtlLengthSid(Owner->Owner); status = RtlSafeAddULONG(&requiredSize, requiredSize, size); GOTO_CLEANUP_ON_STATUS(status); } if (PrimaryGroup->PrimaryGroup) { size = RtlLengthSid(PrimaryGroup->PrimaryGroup); status = RtlSafeAddULONG(&requiredSize, requiredSize, size); GOTO_CLEANUP_ON_STATUS(status); } if (DefaultDacl->DefaultDacl) { status = RtlSafeAddULONG(&requiredSize, requiredSize, DefaultDacl->DefaultDacl->AclSize); GOTO_CLEANUP_ON_STATUS(status); } status = RtlSafeMultiplyULONG(&size, sizeof(Groups->Groups[0]), Groups->GroupCount); GOTO_CLEANUP_ON_STATUS(status); status = RtlSafeAddULONG(&requiredSize, requiredSize, size); GOTO_CLEANUP_ON_STATUS(status); for (i = 0; i < Groups->GroupCount; i++) { size = RtlLengthSid(Groups->Groups[i].Sid); status = RtlSafeAddULONG(&requiredSize, requiredSize, size); GOTO_CLEANUP_ON_STATUS(status); } status = RtlSafeMultiplyULONG(&size, sizeof(Privileges->Privileges[0]), Privileges->PrivilegeCount); GOTO_CLEANUP_ON_STATUS(status); status = RtlSafeAddULONG(&requiredSize, requiredSize, size); GOTO_CLEANUP_ON_STATUS(status); status = RTL_ALLOCATE(&token, ACCESS_TOKEN, requiredSize); GOTO_CLEANUP_ON_STATUS(status); location = LW_PTR_ADD(token, sizeof(*token)); // Initialize token->ReferenceCount = 1; token->Flags = 0; unixError = pthread_rwlock_init(&token->RwLock, NULL); if (unixError) { LW_RTL_LOG_ERROR("Failed to init rwlock in access token " "(error = %d).", unixError); status = LwErrnoToNtStatus(unixError); GOTO_CLEANUP(); } token->pRwLock = &token->RwLock; token->User.Attributes = User->User.Attributes; token->User.Sid = (PSID) location; location = RtlpAppendData(location, User->User.Sid, RtlLengthSid(User->User.Sid)); token->GroupCount = Groups->GroupCount; token->Groups = (PSID_AND_ATTRIBUTES) location; location = LwRtlOffsetToPointer(location, sizeof(Groups->Groups[0]) * Groups->GroupCount); for (i = 0; i < Groups->GroupCount; i++) { token->Groups[i].Attributes = Groups->Groups[i].Attributes; token->Groups[i].Sid = (PSID) location; location = RtlpAppendData(location, Groups->Groups[i].Sid, RtlLengthSid(Groups->Groups[i].Sid)); } token->PrivilegeCount = Privileges->PrivilegeCount; token->Privileges = (PLUID_AND_ATTRIBUTES) location; location = LwRtlOffsetToPointer( location, sizeof(Privileges->Privileges[0]) * Privileges->PrivilegeCount); memcpy(token->Privileges, Privileges->Privileges, sizeof(token->Privileges[0]) * token->PrivilegeCount); if (Owner->Owner) { token->Owner = (PSID) location; location = RtlpAppendData(location, Owner->Owner, RtlLengthSid(Owner->Owner)); } if (PrimaryGroup->PrimaryGroup) { token->PrimaryGroup = (PSID) location; location = RtlpAppendData(location, PrimaryGroup->PrimaryGroup, RtlLengthSid(PrimaryGroup->PrimaryGroup)); } if (DefaultDacl->DefaultDacl) { token->DefaultDacl = (PACL) location; location = RtlpAppendData(location, DefaultDacl->DefaultDacl, DefaultDacl->DefaultDacl->AclSize); } if (Unix) { SetFlag(token->Flags, ACCESS_TOKEN_FLAG_UNIX_PRESENT); token->Uid = Unix->Uid; token->Gid = Unix->Gid; token->Umask = Unix->Umask; } if (location != LW_PTR_ADD(token, requiredSize)) { status = STATUS_ASSERTION_FAILURE; GOTO_CLEANUP(); } status = STATUS_SUCCESS; cleanup: if (!NT_SUCCESS(status)) { RtlReleaseAccessToken(&token); } *AccessToken = token; return status; }