diff options
Diffstat (limited to 'src/vm/securitymeta.cpp')
-rw-r--r-- | src/vm/securitymeta.cpp | 2318 |
1 files changed, 2318 insertions, 0 deletions
diff --git a/src/vm/securitymeta.cpp b/src/vm/securitymeta.cpp new file mode 100644 index 0000000000..980958d49f --- /dev/null +++ b/src/vm/securitymeta.cpp @@ -0,0 +1,2318 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. +//-------------------------------------------------------------------------- +// securitymeta.cpp +// +//pre-computes security meta information, from declarative and run-time information +// + + +// +//-------------------------------------------------------------------------- + + + +#include "common.h" + +#include "object.h" +#include "excep.h" +#include "vars.hpp" +#include "security.h" + +#include "perfcounters.h" +#include "nlstable.h" +#include "frames.h" +#include "dllimport.h" +#include "strongname.h" +#include "eeconfig.h" +#include "field.h" +#include "threads.h" +#include "eventtrace.h" +#ifdef FEATURE_REMOTING +#include "appdomainhelper.h" +#include "objectclone.h" +#endif //FEATURE_REMOTING +#include "typestring.h" +#include "stackcompressor.h" +#include "securitydeclarative.h" +#include "customattribute.h" +#include "../md/compiler/custattr.h" + +#include "securitymeta.h" +#include "caparser.h" + +void FieldSecurityDescriptor::VerifyDataComputed() +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + SO_INTOLERANT; + } + CONTRACTL_END; + + if (m_flags & FieldSecurityDescriptorFlags_IsComputed) + { + return; + } + +#ifndef FEATURE_CORECLR + FieldSecurityDescriptorTransparencyEtwEvents etw(this); +#endif // !FEATURE_CORECLR + +#ifdef _DEBUG + // If we've setup a breakpoint when we compute the transparency of this field, then stop in the debugger + // now. + static ConfigMethodSet fieldTransparencyBreak; + fieldTransparencyBreak.ensureInit(CLRConfig::INTERNAL_Security_TransparencyFieldBreak); + if (fieldTransparencyBreak.contains(m_pFD->GetName(), m_pFD->GetApproxEnclosingMethodTable()->GetDebugClassName())) + { + DebugBreak(); + } +#endif // _DEBUG + + FieldSecurityDescriptorFlags fieldFlags = FieldSecurityDescriptorFlags_None; + + // check to see if the class has the critical attribute + MethodTable* pMT = m_pFD->GetApproxEnclosingMethodTable(); + TypeSecurityDescriptor typeSecDesc(pMT); + + const SecurityTransparencyBehavior *pTransparencyBehavior = m_pFD->GetModule()->GetAssembly()->GetSecurityTransparencyBehavior(); + _ASSERTE(pTransparencyBehavior); + + TokenSecurityDescriptor tokenSecDesc(m_pFD->GetModule(), m_pFD->GetMemberDef()); + + // If the containing type is all transparent or all critical / safe critical, then the field must also be + // transparent or critical / safe critical. If the type is mixed, then we need to look at the field's + // token first to see what its transparency level is + if (typeSecDesc.IsAllTransparent()) + { + fieldFlags = FieldSecurityDescriptorFlags_None; + } + else if (typeSecDesc.IsOpportunisticallyCritical()) + { + // Field opportunistically critical rules: + // Level 1 -> safe critical + // Level 2 -> critical + // If the containing type is participating in type equivalence -> transparent + + if (!typeSecDesc.IsTypeEquivalent()) + { + fieldFlags |= FieldSecurityDescriptorFlags_IsCritical; + + if (typeSecDesc.IsTreatAsSafe() || pTransparencyBehavior->DoesOpportunisticRequireOnlySafeCriticalMethods()) + { + fieldFlags |= FieldSecurityDescriptorFlags_IsTreatAsSafe; + } + } + } + else if (typeSecDesc.IsAllCritical()) + { + fieldFlags |= FieldSecurityDescriptorFlags_IsCritical; + + if (typeSecDesc.IsTreatAsSafe()) + { + fieldFlags |= FieldSecurityDescriptorFlags_IsTreatAsSafe; + } + else if (pTransparencyBehavior->CanIntroducedCriticalMembersAddTreatAsSafe() && + (tokenSecDesc.GetMetadataFlags() & (TokenSecurityDescriptorFlags_TreatAsSafe | TokenSecurityDescriptorFlags_SafeCritical))) + { + // If the transparency model allows members introduced into a critical scope to add their own + // TreatAsSafe attributes, then we need to look for a token level TreatAsSafe as well. + fieldFlags |= FieldSecurityDescriptorFlags_IsTreatAsSafe; + } + } + else + { + fieldFlags |= pTransparencyBehavior->MapFieldAttributes(tokenSecDesc.GetMetadataFlags()); + } + + // TreatAsSafe from the type we're contained in always propigates to its fields + if ((fieldFlags & FieldSecurityDescriptorFlags_IsCritical) && + typeSecDesc.IsTreatAsSafe()) + { + fieldFlags |= FieldSecurityDescriptorFlags_IsTreatAsSafe; + } + + // If the field is public and critical, it may additionally need to be marked treat as safe + if (pTransparencyBehavior->DoesPublicImplyTreatAsSafe() && + typeSecDesc.IsTypeExternallyVisibleForTransparency() && + (m_pFD->IsPublic() || m_pFD->IsProtected() || IsFdFamORAssem(m_pFD->GetFieldProtection())) && + (fieldFlags & FieldSecurityDescriptorFlags_IsCritical) && + !(fieldFlags & FieldSecurityDescriptorFlags_IsTreatAsSafe)) + { + fieldFlags |= FieldSecurityDescriptorFlags_IsTreatAsSafe; + } + + // mark computed + FastInterlockOr(reinterpret_cast<DWORD *>(&m_flags), fieldFlags | FieldSecurityDescriptorFlags_IsComputed); +} + + +// All callers to his method will pass in a valid memory location for pMethodSecurityDesc which they are responsible for +// free-ing when done using it. Typically this will be a stack location for perf reasons. +// +// Some details about when we cache MethodSecurityDescriptors and how the linkdemand process works: +// - When we perform the LinkTimeCheck, we follow this order of checks +// : APTCA check +// : Class-level declarative security using TypeSecurityDescriptor +// : Method-level declarative security using MethodSecurityDescriptor +// : Unmanaged-code check (if required) +// +// For APTCA and Unmanaged code checks, we don't have a permissionset entry in the hashtable that we use when performing the demand. Since +// these are well-known demands, we special-case them. What this means is that we may have a MethodSecurityDescriptor that requires a linktime check +// but does not have DeclActionInfo or TokenDeclActionInfo fields inside. +// +// For cases where the Type causes the Link/Inheritance demand, the MethodDesc has the flag set, but the MethodSecurityDescriptor will not have any +// DeclActionInfo or TokenDeclActionInfo. +// +// And the relevance all this has to this method is the following: Don't automatically insert a MethodSecurityDescriptor into the hash table if it has +// linktime or inheritance time check. Only do so if either of the DeclActionInfo or TokenDeclActionInfo fields are non-NULL. +void MethodSecurityDescriptor::LookupOrCreateMethodSecurityDescriptor(MethodSecurityDescriptor* ret_methSecDesc) +{ + CONTRACTL { + THROWS; + GC_TRIGGERS; + MODE_ANY; + PRECONDITION(CheckPointer(ret_methSecDesc)); + } CONTRACTL_END; + + _ASSERTE(CanMethodSecurityDescriptorBeCached(ret_methSecDesc->m_pMD)); + + MethodSecurityDescriptor* pMethodSecurityDesc = (MethodSecurityDescriptor*)TokenSecurityDescriptor::LookupSecurityDescriptor(ret_methSecDesc->m_pMD); + if (pMethodSecurityDesc == NULL) + { + ret_methSecDesc->VerifyDataComputedInternal();// compute all the data that is needed. + + // cache method security desc using some simple heuristics + // we have some token actions computed, let us cache this method security desc + + if (ret_methSecDesc->GetRuntimeDeclActionInfo() != NULL || + ret_methSecDesc->GetTokenDeclActionInfo() != NULL || + // NGEN accesses MethodSecurityDescriptors frequently to check for security callouts + IsCompilationProcess()) + { + + // Need to insert this methodSecDesc + LPVOID pMem = GetAppDomain()->GetLowFrequencyHeap()->AllocMem(S_SIZE_T(sizeof(MethodSecurityDescriptor))); + + // allocate a method security descriptor, using the appdomain heap memory + pMethodSecurityDesc = new (pMem) MethodSecurityDescriptor(ret_methSecDesc->m_pMD); + + *pMethodSecurityDesc = *ret_methSecDesc; // copy over the fields + + MethodSecurityDescriptor* pExistingMethodSecurityDesc = NULL; + // insert pMethodSecurityDesc into our hash table + pExistingMethodSecurityDesc = reinterpret_cast<MethodSecurityDescriptor*>(TokenSecurityDescriptor::InsertSecurityDescriptor(ret_methSecDesc->m_pMD, (HashDatum) pMethodSecurityDesc)); + if (pExistingMethodSecurityDesc != NULL) + { + // if we found an existing method security desc, use it + // no need to delete the one we had created, as we allocated it in the Appdomain heap + pMethodSecurityDesc = pExistingMethodSecurityDesc; + } + } + } + else + { + *ret_methSecDesc = *pMethodSecurityDesc; + } + + return; +} + +BOOL MethodSecurityDescriptor::CanMethodSecurityDescriptorBeCached(MethodDesc* pMD) +{ + LIMITED_METHOD_CONTRACT; + + return pMD->IsInterceptedForDeclSecurity() || + pMD->RequiresLinktimeCheck() || + pMD->RequiresInheritanceCheck()|| + pMD->IsVirtual()|| + pMD->IsMethodImpl()|| + pMD->IsLCGMethod(); +} + +void MethodSecurityDescriptor::VerifyDataComputedInternal() +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + INJECT_FAULT(COMPlusThrowOM();); + } + CONTRACTL_END; + + if (m_flags & MethodSecurityDescriptorFlags_IsComputed) + { + return; + } + + // If the method hasn't already cached it's transparency information, then we need to calculate it here. + // It can be cached if we're loading the method from a native image, but are creating the security + // descriptor in order to figure out declarative security. + if (!m_pMD->HasCriticalTransparentInfo()) + { + ComputeCriticalTransparentInfo(); + } + + // compute RUN-TIME DECLARATIVE SECURITY STUFF + // (merges both class and method level run-time declarative security info). + if (HasRuntimeDeclarativeSecurity()) + { + ComputeRuntimeDeclarativeSecurityInfo(); + } + + // compute method specific DECLARATIVE STUFF + if (HasRuntimeDeclarativeSecurity() || HasLinkOrInheritanceDeclarativeSecurity()) + { + ComputeMethodDeclarativeSecurityInfo(); + } + + // mark computed + FastInterlockOr(reinterpret_cast<DWORD *>(&m_flags), MethodSecurityDescriptorFlags_IsComputed); +} + +void MethodSecurityDescriptor::ComputeCriticalTransparentInfo() +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + SO_INTOLERANT; + } + CONTRACTL_END; + +#ifndef FEATURE_CORECLR + MethodSecurityDescriptorTransparencyEtwEvents etw(this); +#endif // !FEATURE_CORECLR + + MethodTable* pMT = m_pMD->GetMethodTable(); + +#ifdef _DEBUG + // If we've setup a breakpoint when we compute the transparency of this method, then stop in the debugger + // now. + static ConfigMethodSet methodTransparencyBreak; + methodTransparencyBreak.ensureInit(CLRConfig::INTERNAL_Security_TransparencyMethodBreak); + if (methodTransparencyBreak.contains(m_pMD->GetName(), pMT->GetDebugClassName())) + { + DebugBreak(); + } +#endif // _DEBUG + + MethodSecurityDescriptorFlags methodFlags = MethodSecurityDescriptorFlags_None; + TypeSecurityDescriptor typeSecDesc(pMT); + + const SecurityTransparencyBehavior *pTransparencyBehavior = m_pMD->GetAssembly()->GetSecurityTransparencyBehavior(); + _ASSERTE(pTransparencyBehavior); + + // If the transparency model used by this method cares about the location of the introduced method, + // then we need to figure out where the method was introduced. This is only important when the type is + // all critical or opportunistically critical, since otherwise we'll look at the method directly anyway. + MethodDesc *pIntroducingMD = NULL; + bool fWasIntroducedLocally = true; + if (pTransparencyBehavior->DoesScopeApplyOnlyToIntroducedMethods() && + (typeSecDesc.IsOpportunisticallyCritical() || typeSecDesc.IsAllCritical())) + { + if (m_pMD->IsVirtual() && + !m_pMD->IsInterface() && + m_pMD->GetSlot() < m_pMD->GetMethodTable()->GetNumVirtuals()) + { + pIntroducingMD = m_pMD->GetMethodTable()->GetIntroducingMethodDesc(m_pMD->GetSlot()); + } + + fWasIntroducedLocally = pIntroducingMD == NULL || pIntroducingMD == m_pMD; + + // + // #OpportunisticallyCriticalMultipleImplement + // + // One method can be the target of multiple interfaces and also an override of a base class. Further, + // there could be conflicting inheritance requirements; for instance overriding a critical method and + // implementing a transparent interface with the same method desc. + // + // For APTCA assemblies, we require that they seperate out to explicit interface implementations to + // solve this problem, however we cannot push this requirement to opportunistically critical + // assemblies. Therefore, in those assemblies we create the following non-introduced method rule: + // + // 1. If both the base override and all of the interfaces that a method desc is implementing have the + // same accessibility, then the method must agree with that accessibility. + // + // 2. If there is a mix of transparent accessibilities, then the method desc will be safe critical. + // This leads to a situation where a safe critical method can implement a critical interface, + // which is not a security hole, but does create some strangeness around the fact that transparent + // code can call the method directly but not via the interface (or base type). + // + // Since there is no way for all inheritance requirements to be satisfied here, we choose to + // violate the overriding critical one because looking directly at the method will indicate that + // it is callable from transparent, whereas allowing a critical implementation of a transparent + // interface would create a worse situation of the method desc saying that it is not callable from + // transparent, while it would be via the interface. + // + // A variation of this problem can also occur with MethodImpls. For example, a virtual method could + // implement both a transparent and a critical virtual. This case follows the same rules laid out + // above for interface implementations. + + // We need to check the interfaces and MethodImpls if we were introduced locally, or if we're + // opportunistically critical and the introducing method was not safe critical. + bool fCheckInterfacesAndMethodImpls = fWasIntroducedLocally; + if (!fCheckInterfacesAndMethodImpls && typeSecDesc.IsOpportunisticallyCritical()) + { + _ASSERTE(pIntroducingMD != NULL); + // Make sure the introducing method has its transparency calculated + if (!pIntroducingMD->HasCriticalTransparentInfo()) + { + MethodSecurityDescriptor introducingMSD(pIntroducingMD); + introducingMSD.ComputeCriticalTransparentInfo(); + } + + // We need to keep looking at the interfaces and MethodImpls if we override a critical method. If + // we're overriding a safe critical or transparent method, then we'll end up being safe critical + // anyway. + fCheckInterfacesAndMethodImpls = pIntroducingMD->IsCritical() && !pIntroducingMD->IsTreatAsSafe(); + } + + if (fCheckInterfacesAndMethodImpls && + !m_pMD->IsCtor() && + !m_pMD->IsStatic()) + { + // Interface implementation or MethodImpl that we choose to use to calculate transparency - for + // opportunistically critical methods, this is the first safe critical / transparent method if one + // is found, otherwise the first critical method. For all other methods, it is the first + // interface / MethodImpl method found. + MethodDesc *pSelectedMD = NULL; + + // Iterate over the implemented methods to see if we're implementing any interfaces or virtuals + MethodImplementationIterator implementationIterator(m_pMD); + bool fFoundTargetMethod = false; + for (; implementationIterator.IsValid() && !fFoundTargetMethod; implementationIterator.Next()) + { + MethodDesc *pImplementedMD = implementationIterator.Current(); + + // If we're opportunistically critical, then we need to figure out if the implemented + // method is critical or not, and continue looking if we only found critical methods + // to this point. + if (typeSecDesc.IsOpportunisticallyCritical()) + { + // We should either have not found a candidate yet, or that candidate should be critical + _ASSERTE(pSelectedMD == NULL || + (pSelectedMD->IsCritical() && !pSelectedMD->IsTreatAsSafe())); + + if (!pImplementedMD->HasCriticalTransparentInfo()) + { + MethodSecurityDescriptor implementedMSD(pImplementedMD); + implementedMSD.ComputeCriticalTransparentInfo(); + } + + // If this is the first interface method or MethodImpl we've seen, save it away. Otherwise, + // we've so far implemented only critical interfaces and methods, so if we see a + // transparent or safe critical interface method, we should note that and stop looking + // further. + if (!pImplementedMD->IsCritical() || pImplementedMD->IsTreatAsSafe()) + { + pSelectedMD = pImplementedMD; + fFoundTargetMethod = true; + } + else if (pSelectedMD == NULL) + { + pSelectedMD = pImplementedMD; + } + } + else + { + // If we're not opportunistically critical, then we only care about the first interface + // implementation or MethodImpl that we see. + _ASSERTE(pSelectedMD == NULL); + pSelectedMD = pImplementedMD; + fFoundTargetMethod = true; + } + } + + // If we found an interface method or MethodImpl, then use that as the introducing method + if (pSelectedMD != NULL) + { + pIntroducingMD = pSelectedMD; + fWasIntroducedLocally = false; + } + } + + // If we're not working with a method that we introduced, make sure it has its transparency calculated + // before we need to use it. + if (!fWasIntroducedLocally && !pIntroducingMD->HasCriticalTransparentInfo()) + { + MethodSecurityDescriptor introducingMSD(pIntroducingMD); + introducingMSD.ComputeCriticalTransparentInfo(); + _ASSERTE(pIntroducingMD->HasCriticalTransparentInfo()); + } + } + + // In a couple of cases we know the transparency of the method directly: + // 1. If our parent type is all transparent, we must also be transparent + // 2. If we're opprotunstically critical, then we can figure out the annotation based upon the override + // 3. If our parent type is all critical, and we were introduced by that type, we must also be critical + // (we could also be safe critical as well). + // + // Otherwise, we need to ask the current transparency implementation what this method is, because it + // will vary depending upon if we're in legacy mode or not. + TokenSecurityDescriptor methodTokenSecDesc(m_pMD->GetModule(), GetToken()); + if (typeSecDesc.IsAllTransparent()) + { + methodFlags = MethodSecurityDescriptorFlags_None; + } + else if (typeSecDesc.IsOpportunisticallyCritical()) + { + // Opportunistically critical methods will always be critical + methodFlags |= MethodSecurityDescriptorFlags_IsCritical; + + // If we're overriding a safe critical or transparent method, we also need to be treat as safe + // + // Virtuals on value types have multiple entries in the method table, so we may not have mapped + // it back to the override that it was implementing. In order to compensate for this, we simply + // allow all virtuals in opportunistically critical value types to be safe critical. This doesn't + // introduce any extra risk, because unless we're overriding one of the Object overloads, there is + // nothing that transparent code can cast the ValueType to in order to access the virtual since the + // value type itself will be critical. + // + // If we're in a transparency model where all opportunistically critical methods are safe critical, we + // need to add the treat as safe bit. + // + // Finally, if we're in a type participating in type equivalence, then we need to add the treat as + // safe bit. This keeps the transparency of methods in type equivalent interfaces consistent across + // security rule sets in opportunistically critical assemblies, which allows types from v2 PIAs to + // be embedded successfully into v4 assemblies for instance. + if (!fWasIntroducedLocally && + (!pIntroducingMD->IsCritical() || pIntroducingMD->IsTreatAsSafe())) + { + methodFlags |= MethodSecurityDescriptorFlags_IsTreatAsSafe; + } + else if (pMT->IsValueType() && m_pMD->IsVirtual()) + { + methodFlags |= MethodSecurityDescriptorFlags_IsTreatAsSafe; + } + else if (pTransparencyBehavior->DoesOpportunisticRequireOnlySafeCriticalMethods()) + { + methodFlags |= MethodSecurityDescriptorFlags_IsTreatAsSafe; + } + else if (typeSecDesc.IsTypeEquivalent()) + { + methodFlags |= MethodSecurityDescriptorFlags_IsTreatAsSafe; + } + } + else if (typeSecDesc.IsAllCritical() && fWasIntroducedLocally) + { + methodFlags |= MethodSecurityDescriptorFlags_IsCritical; + + if (typeSecDesc.IsTreatAsSafe()) + { + methodFlags |= MethodSecurityDescriptorFlags_IsTreatAsSafe; + } + else if (pTransparencyBehavior->CanIntroducedCriticalMembersAddTreatAsSafe() && + (methodTokenSecDesc.GetMetadataFlags() & (TokenSecurityDescriptorFlags_TreatAsSafe | TokenSecurityDescriptorFlags_SafeCritical))) + { + // If the transparency model allows members introduced into a critical scope to add their own + // TreatAsSafe attributes, then we need to look for a token level TreatAsSafe as well. + methodFlags |= MethodSecurityDescriptorFlags_IsTreatAsSafe; + } + } + else + { + // We don't have a larger scope that tells us what to do with the method, so ask the transparency + // implementation to map our attributes to a set of flags + methodFlags |= pTransparencyBehavior->MapMethodAttributes(methodTokenSecDesc.GetMetadataFlags()); + } + + // TreatAsSafe from the type we're contained in always propigates to its methods + if (fWasIntroducedLocally && + (methodFlags & MethodSecurityDescriptorFlags_IsCritical) && + typeSecDesc.IsTreatAsSafe()) + { + methodFlags |= MethodSecurityDescriptorFlags_IsTreatAsSafe; + } + + // The compiler can introduce default constructors implicitly, and for an explicitly critical type they + // will always be transparent - resulting in a type load exception. If we are a transparent default .ctor + // of an explicitly critical type, then we'll switch to being safe critical to allow the type to load and + // allow us access to our this pointer + if (!typeSecDesc.IsAllCritical() && + typeSecDesc.IsCritical() && + !(methodFlags & MethodSecurityDescriptorFlags_IsCritical) && + m_pMD->IsCtor()) + { + if (pMT->HasDefaultConstructor() && + pMT->GetDefaultConstructor() == m_pMD) + { + methodFlags |= MethodSecurityDescriptorFlags_IsCritical | + MethodSecurityDescriptorFlags_IsTreatAsSafe; + } + } + + // See if we're a public critical method, then we may need to additionally make ourselves treat as safe + if (pTransparencyBehavior->DoesPublicImplyTreatAsSafe() && + typeSecDesc.IsTypeExternallyVisibleForTransparency() && + (m_pMD->IsPublic() || m_pMD->IsProtected() || IsMdFamORAssem(m_pMD->GetAttrs())) && + (methodFlags & MethodSecurityDescriptorFlags_IsCritical) && + !(methodFlags & MethodSecurityDescriptorFlags_IsTreatAsSafe)) + { + methodFlags |= MethodSecurityDescriptorFlags_IsTreatAsSafe; + } + + // Cache our state on the MethodDesc + m_pMD->SetCriticalTransparentInfo(methodFlags & MethodSecurityDescriptorFlags_IsCritical, + methodFlags & MethodSecurityDescriptorFlags_IsTreatAsSafe); +} + +void MethodSecurityDescriptor::ComputeRuntimeDeclarativeSecurityInfo() +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + INJECT_FAULT(COMPlusThrowOM();); + } + CONTRACTL_END; + + // Load declarative security attributes + _ASSERTE(HasRuntimeDeclarativeSecurity()); + m_declFlagsDuringPreStub = m_pMD->GetSecurityFlagsDuringPreStub(); + _ASSERTE(m_declFlagsDuringPreStub && " Expected some runtime security action"); + m_pRuntimeDeclActionInfo = SecurityDeclarative::DetectDeclActions(m_pMD, m_declFlagsDuringPreStub); +} + +void MethodSecurityDescriptor::ComputeMethodDeclarativeSecurityInfo() +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + INJECT_FAULT(COMPlusThrowOM();); + } + CONTRACTL_END; + + DWORD flags = 0; + + _ASSERTE(HasRuntimeDeclarativeSecurity()|| HasLinkOrInheritanceDeclarativeSecurity()); + DWORD dwDeclFlags; + HRESULT hr = SecurityDeclarative::GetDeclarationFlags(GetIMDInternalImport(), GetToken(), &dwDeclFlags, NULL, NULL); + + if (SUCCEEDED(hr)) + { + GCX_COOP(); + PsetCacheEntry *tokenSetIndexes[dclMaximumValue + 1]; + SecurityDeclarative::DetectDeclActionsOnToken(GetToken(), dwDeclFlags, tokenSetIndexes, GetIMDInternalImport()); + + // Create single linked list of set indexes + DWORD dwLocalAction; + bool builtInCASPermsOnly = TRUE; + for (dwLocalAction = 0; dwLocalAction <= dclMaximumValue; dwLocalAction++) + { + if (tokenSetIndexes[dwLocalAction] != NULL) + { + TokenDeclActionInfo::LinkNewDeclAction(&m_pTokenDeclActionInfo, (CorDeclSecurity)dwLocalAction, tokenSetIndexes[dwLocalAction]); + builtInCASPermsOnly = builtInCASPermsOnly && (tokenSetIndexes[dwLocalAction]->ContainsBuiltinCASPermsOnly(dwLocalAction)); + } + } + + if (builtInCASPermsOnly) + flags |= MethodSecurityDescriptorFlags_IsBuiltInCASPermsOnly; + SecurityProperties sp(dwDeclFlags); + if (sp.FDemandsOnly()) + flags |= MethodSecurityDescriptorFlags_IsDemandsOnly; + if (sp.FAssertionsExist()) + { + // Do a check to see if the assembly has been granted permission to assert and let's cache that value in the MethodSecurityDesriptor + Module* pModule = m_pMD->GetModule(); + PREFIX_ASSUME_MSG(pModule != NULL, "Should be a Module pointer here"); + + if (Security::CanAssert(pModule)) + { + flags |= MethodSecurityDescriptorFlags_AssertAllowed; + } + } + } + + FastInterlockOr(reinterpret_cast<DWORD *>(&m_flags), flags); +} + +void MethodSecurityDescriptor::InvokeInheritanceChecks(MethodDesc *pChildMD) +{ + CONTRACTL + { + STANDARD_VM_CHECK; + PRECONDITION(CheckPointer(pChildMD)); + } + CONTRACTL_END; + + const SecurityTransparencyBehavior *pTransparencyBehavior = pChildMD->GetAssembly()->GetSecurityTransparencyBehavior(); + if (pTransparencyBehavior->AreInheritanceRulesEnforced() && Security::IsTransparencyEnforcementEnabled()) + { + // The profiler may want to suppress these checks if it's currently running on the child type + if (Security::BypassSecurityChecksForProfiler(pChildMD)) + { + return; + } + + /* + Allowed Inheritance Patterns (cannot change accessibility) + ---------------------------- + + Base Class/Method Derived Class/ Method + ----------------- --------------------- + Transparent Transparent + Transparent SafeCritical + SafeCritical SafeCritical + SafeCritical Transparent + Critical Critical + + + Disallowed Inheritance patterns + ------------------------------- + + Base Class/Method Derived Class /Method + ----------------- --------------------- + Transparent Critical + SafeCritical Critical + Critical Transparent + Critical SafeCritical + */ + + MethodSecurityDescriptor methSecurityDescriptor(pChildMD, FALSE); + TokenSecurityDescriptor methTokenSecurityDescriptor(pChildMD->GetModule(), pChildMD->GetMemberDef()); + if (IsCritical()) + { + if (IsTreatAsSafe()) + { + // Base: SafeCritical. Check if Child is Critical + if (methSecurityDescriptor.IsCritical() && !methSecurityDescriptor.IsTreatAsSafe()) + { +#ifdef _DEBUG + if (g_pConfig->LogTransparencyErrors()) + { + SecurityTransparent::LogTransparencyError(pChildMD, "Critical method overriding a SafeCritical base method", m_pMD); + } +#endif // _DEBUG + SecurityTransparent::ThrowTypeLoadException(pChildMD); + } + } + else + { + // Base: Critical. + if (!methSecurityDescriptor.IsCritical()) + { + // Child is transparent + // throw +#ifdef _DEBUG + if (g_pConfig->LogTransparencyErrors()) + { + SecurityTransparent::LogTransparencyError(pChildMD, "Transparent method overriding a critical base method", m_pMD); + } +#endif // _DEBUG + SecurityTransparent::ThrowTypeLoadException(pChildMD); + } + else if (methSecurityDescriptor.IsTreatAsSafe() && !methSecurityDescriptor.IsOpportunisticallyCritical()) + { + // The child is safe critical and not opportunistically critical (see code:#OpportunisticallyCriticalMultipleImplement) + // throw. +#ifdef _DEBUG + if (g_pConfig->LogTransparencyErrors()) + { + SecurityTransparent::LogTransparencyError(pChildMD, "Safe critical method overriding a SafeCritical base method", m_pMD); + } +#endif // _DEBUG + SecurityTransparent::ThrowTypeLoadException(pChildMD); + } + } + } + else + { + // Base: Transparent. Throw if derived is Critical and not SafeCritical + if (methSecurityDescriptor.IsCritical() && !methSecurityDescriptor.IsTreatAsSafe()) + { +#ifdef _DEBUG + if (g_pConfig->LogTransparencyErrors()) + { + SecurityTransparent::LogTransparencyError(pChildMD, "Critical method overriding a transparent base method", m_pMD); + } +#endif // _DEBUG + SecurityTransparent::ThrowTypeLoadException(pChildMD); + } + } + } + +#ifndef FEATURE_CORECLR + // Check CAS Inheritance + + // Early out if we're fully trusted + if (SecurityDeclarative::FullTrustCheckForLinkOrInheritanceDemand(pChildMD->GetAssembly())) + { + return; + } + + if (HasInheritanceDeclarativeSecurity()) + { +#ifdef CROSSGEN_COMPILE + // NGen is always full trust. This path should be unreachable. + CrossGenNotSupported("HasInheritanceDeclarativeSecurity()"); +#else // CROSSGEN_COMPILE + GCX_COOP(); + + OBJECTREF refCasDemands = NULL; + PsetCacheEntry* pCasDemands = NULL; + + HRESULT hr = GetDeclaredPermissionsWithCache(dclInheritanceCheck, &refCasDemands, &pCasDemands); + if (refCasDemands != NULL) + { + _ASSERTE(pCasDemands != NULL); + + // See if inheritor's assembly has passed this demand before + AssemblySecurityDescriptor *pInheritorAssem = static_cast<AssemblySecurityDescriptor*>(pChildMD->GetAssembly()->GetSecurityDescriptor()); + BOOL fSkipCheck = pInheritorAssem->AlreadyPassedDemand(pCasDemands); + + if (!fSkipCheck) + { + GCPROTECT_BEGIN(refCasDemands); + + // Perform the check (it's really just a LinkDemand) + SecurityStackWalk::LinkOrInheritanceCheck(pChildMD->GetAssembly()->GetSecurityDescriptor(), refCasDemands, pChildMD->GetAssembly(), dclInheritanceCheck); + + // Demand passed. Add it to the Inheritor's assembly's list of passed demands + pInheritorAssem->TryCachePassedDemand(pCasDemands); + + GCPROTECT_END(); + } + } + + // @todo -- non cas shouldn't be used for inheritance demands... + + // Check non-CAS Inheritance + OBJECTREF refNonCasDemands = NULL; + hr = GetDeclaredPermissionsWithCache( dclNonCasInheritance, &refNonCasDemands, NULL); + if (refNonCasDemands != NULL) + { + _ASSERTE(((PERMISSIONSETREF)refNonCasDemands)->CheckedForNonCas() && "Declarative permissions should have been checked for nonCAS in PermissionSet.CreateSerialized"); + if (((PERMISSIONSETREF)refNonCasDemands)->ContainsNonCas()) + { + GCPROTECT_BEGIN(refNonCasDemands); + + // Perform the check + MethodDescCallSite demand(METHOD__PERMISSION_SET__DEMAND_NON_CAS, &refNonCasDemands); + ARG_SLOT arg = ObjToArgSlot(refNonCasDemands); + demand.Call(&arg); + + GCPROTECT_END(); + } + } +#endif // CROSSGEN_COMPILE + } +#endif // FEATURE_CORECLR +} + +MethodSecurityDescriptor::MethodImplementationIterator::MethodImplementationIterator(MethodDesc *pMD) + : m_interfaceIterator(pMD->GetMethodTable()), + m_pMD(pMD), + m_iMethodImplIndex(0), + m_fInterfaceIterationBegun(false), + m_fMethodImplIterationBegun(false) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + SO_INTOLERANT; + PRECONDITION(pMD != NULL); + } + CONTRACTL_END; + + Next(); +} + +MethodDesc *MethodSecurityDescriptor::MethodImplementationIterator::Current() +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + SO_INTOLERANT; + PRECONDITION(IsValid()); + } + CONTRACTL_END; + + if (m_pMD->GetMethodTable()->HasDispatchMap() && m_interfaceIterator.IsValid()) + { + _ASSERTE(m_fInterfaceIterationBegun); + MethodTable *pInterface = m_pMD->GetMethodTable()->LookupDispatchMapType(m_interfaceIterator.Entry()->GetTypeID()); + return pInterface->GetMethodDescForSlot(m_interfaceIterator.Entry()->GetSlotNumber()); + } + else + { + _ASSERTE(m_fMethodImplIterationBegun); + _ASSERTE(m_pMD->IsMethodImpl()); + _ASSERTE(m_iMethodImplIndex < m_pMD->GetMethodImpl()->GetSize()); + return m_pMD->GetMethodImpl()->GetImplementedMDs()[m_iMethodImplIndex]; + } +} + +bool MethodSecurityDescriptor::MethodImplementationIterator::IsValid() +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + SO_INTOLERANT; + } + CONTRACTL_END; + + // We're valid as long as we still have interface maps or method impls to process + if (m_pMD->GetMethodTable()->HasDispatchMap() && m_interfaceIterator.IsValid()) + { + return true; + } + else if (m_pMD->IsMethodImpl()) + { + return m_iMethodImplIndex < m_pMD->GetMethodImpl()->GetSize(); + } + else + { + return false; + } +} + +void MethodSecurityDescriptor::MethodImplementationIterator::Next() +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + SO_INTOLERANT; + } + CONTRACTL_END; + + bool fFoundImpl = false; + + // First iterate over the interface implementations + if (m_pMD->GetMethodTable()->HasDispatchMap() && m_interfaceIterator.IsValid()) + { + while (m_interfaceIterator.IsValid() && !fFoundImpl) + { + // If we haven't yet begun iterating interfaces then don't call Next right away - otherwise + // we'll potentially skip over the first interface method. + if (m_fInterfaceIterationBegun) + { + m_interfaceIterator.Next(); + } + else + { + m_fInterfaceIterationBegun = true; + } + + if (m_interfaceIterator.IsValid()) + { + _ASSERTE(!m_interfaceIterator.Entry()->GetTypeID().IsThisClass()); + fFoundImpl = (m_interfaceIterator.Entry()->GetTargetSlotNumber() == m_pMD->GetSlot()); + } + } + } + + // Once we're done with the interface implementations, check for a MethodImpl + if (!fFoundImpl && m_pMD->IsMethodImpl()) + { + MethodImpl * pMethodImpl = m_pMD->GetMethodImpl(); + while ((m_iMethodImplIndex < pMethodImpl->GetSize()) && !fFoundImpl) + { + // If we haven't yet begun iterating method impls then don't move to the next element right away + // - otehrwise we'll potentially skip over the first MethodImpl + if (m_fMethodImplIterationBegun) + { + ++m_iMethodImplIndex; + } + else + { + m_fMethodImplIterationBegun = true; + } + + if (m_iMethodImplIndex < pMethodImpl->GetSize()) + { + // Skip over the interface MethodImpls since we already processed those + fFoundImpl = !pMethodImpl->GetImplementedMDs()[m_iMethodImplIndex]->IsInterface(); + } + } + } +} // MethodSecurityDescriptor::MethodImplementationIterator::Next + +TypeSecurityDescriptor* TypeSecurityDescriptor::GetTypeSecurityDescriptor(MethodTable* pMT) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + PRECONDITION(CheckPointer(pMT)); + } + CONTRACTL_END; + + TypeSecurityDescriptor* pTypeSecurityDesc =NULL; + + + pTypeSecurityDesc = (TypeSecurityDescriptor*)TokenSecurityDescriptor::LookupSecurityDescriptor(pMT); + if (pTypeSecurityDesc == NULL) + { + // didn't find a security descriptor, create one and insert it + LPVOID pMem = GetAppDomain()->GetLowFrequencyHeap()->AllocMem(S_SIZE_T(sizeof(TypeSecurityDescriptor))); + + // allocate a security descriptor, using the appdomain help memory + pTypeSecurityDesc = new (pMem) TypeSecurityDescriptor(pMT); + pTypeSecurityDesc->VerifyDataComputedInternal(); // compute all the data that is needed. + + TypeSecurityDescriptor* pExistingTypeSecurityDesc = NULL; + // insert securitydesc into our hash table + pExistingTypeSecurityDesc = (TypeSecurityDescriptor*)TokenSecurityDescriptor::InsertSecurityDescriptor(pMT, (HashDatum) pTypeSecurityDesc); + if (pExistingTypeSecurityDesc != NULL) + { + // if we found an existing security desc, use it + // no need to delete the one we had created, as we allocated it in the Appdomain help + pTypeSecurityDesc = pExistingTypeSecurityDesc; + } + } + + return pTypeSecurityDesc; +} + +#if !defined(CROSSGEN_COMPILE) && defined(FEATURE_CAS_POLICY) +HRESULT TokenDeclActionInfo::GetDeclaredPermissionsWithCache( + IN CorDeclSecurity action, + OUT OBJECTREF *pDeclaredPermissions, + OUT PsetCacheEntry **pPCE) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + } + CONTRACTL_END; + HRESULT hr = S_OK; + DWORD dwActionFlag = DclToFlag((CorDeclSecurity)action); + + PsetCacheEntry *ptempPCE=NULL; + TokenDeclActionInfo* pCurrentAction = this; + for (; + pCurrentAction; + pCurrentAction = pCurrentAction->pNext) + { + if (pCurrentAction->dwDeclAction == dwActionFlag) + { + ptempPCE = pCurrentAction->pPCE; + break; + } + } + if (pDeclaredPermissions && pCurrentAction) + { + *pDeclaredPermissions = ptempPCE->CreateManagedPsetObject (action); + } + if (pPCE && pCurrentAction) + { + *pPCE = ptempPCE; + } + + return hr; +} + +OBJECTREF TokenDeclActionInfo::GetLinktimePermissions(OBJECTREF *prefNonCasDemands) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + } + CONTRACTL_END; + + OBJECTREF refCasDemands = NULL; + GCPROTECT_BEGIN(refCasDemands); + + GetDeclaredPermissionsWithCache( + dclLinktimeCheck, + &refCasDemands, NULL); + + TokenDeclActionInfo::GetDeclaredPermissionsWithCache( + dclNonCasLinkDemand, + prefNonCasDemands, NULL); + + GCPROTECT_END(); + return refCasDemands; +} + +void TokenDeclActionInfo::InvokeLinktimeChecks(Assembly* pCaller) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + INJECT_FAULT(COMPlusThrowOM();); + PRECONDITION(CheckPointer(pCaller)); + } + CONTRACTL_END; + +#ifdef FEATURE_MULTICOREJIT + + // Reset the flag to allow managed code to be called in multicore JIT background thread from this routine + ThreadStateNCStackHolder holder(-1, Thread::TSNC_CallingManagedCodeDisabled); + +#endif + + struct gc + { + OBJECTREF refNonCasDemands; + OBJECTREF refCasDemands; + } + gc; + ZeroMemory(&gc, sizeof(gc)); + + GCPROTECT_BEGIN(gc); + + // CAS LinkDemands + GetDeclaredPermissionsWithCache(dclLinktimeCheck, + &gc.refCasDemands, + NULL); + + if (gc.refCasDemands != NULL) + { + SecurityStackWalk::LinkOrInheritanceCheck(pCaller->GetSecurityDescriptor(), gc.refCasDemands, pCaller, dclLinktimeCheck); + } + + // NON CAS LinkDEMANDS (we shouldn't support this). + GetDeclaredPermissionsWithCache(dclNonCasLinkDemand, + &gc.refNonCasDemands, + NULL); + + GCPROTECT_END(); +} +#endif // !CROSSGEN_COMPILE && FEATURE_CAS_POLICY + +void TypeSecurityDescriptor::ComputeCriticalTransparentInfo() +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + } + CONTRACTL_END; + +#ifndef FEATURE_CORECLR + TypeSecurityDescriptorTransparencyEtwEvents etw(this); +#endif // !FEATURE_CORECLR + +#ifdef _DEBUG + // If we've setup a breakpoint when we compute the transparency of this type, then stop in the debugger now + SString strTypeTransparencyBreak(CLRConfig::GetConfigValue(CLRConfig::INTERNAL_Security_TransparencyTypeBreak)); + SString strClassName(SString::Utf8, m_pMT->GetDebugClassName()); + if (strTypeTransparencyBreak.EqualsCaseInsensitive(strClassName)) + { + // Do not break in fuzzed assemblies where class name can be empty + if (!strClassName.IsEmpty()) + { + DebugBreak(); + } + } +#endif // _DEBUG + + // check to see if the assembly has the critical attribute + Assembly* pAssembly = m_pMT->GetAssembly(); + _ASSERTE(pAssembly); + ModuleSecurityDescriptor* pModuleSecDesc = ModuleSecurityDescriptor::GetModuleSecurityDescriptor(pAssembly); + pModuleSecDesc->VerifyDataComputed(); + + EEClass *pClass = m_pMT->GetClass(); + TypeSecurityDescriptorFlags typeFlags = TypeSecurityDescriptorFlags_None; + + // If we're contained within another type, then we inherit the transparency of that type. Otherwise we + // check the module to see what type of transparency we have. + if (pClass->IsNested()) + { + // If the type is nested, see if the outer class tells us what our transparency is. Note that we cannot + // use a TypeSecurityDescriptor here since we may still be in the process of loading our outer type. + TokenSecurityDescriptor enclosingTokenSecurityDescriptor(m_pMT->GetModule(), m_pMT->GetEnclosingCl()); + if (enclosingTokenSecurityDescriptor.IsSemanticCritical()) + { + typeFlags |= TypeSecurityDescriptorFlags_IsAllCritical; + } + + // We want to propigate the TreatAsSafe bit even if the outer class is not critical because in the legacy + // transparency model you could have a TAS but not critical type, and the TAS propigated to all nested + // types. + if (enclosingTokenSecurityDescriptor.IsSemanticTreatAsSafe()) + { + typeFlags |= TypeSecurityDescriptorFlags_IsTreatAsSafe; + } + } + + const SecurityTransparencyBehavior *pTransparencyBehavior = m_pMT->GetAssembly()->GetSecurityTransparencyBehavior(); + _ASSERTE(pTransparencyBehavior); + + // If we're not nested, or if the outer type didn't give us enough information to determine what we were, + // then we need to look at the module to see what we are. + if (typeFlags == TypeSecurityDescriptorFlags_None) + { + if (pModuleSecDesc->IsAllTransparent()) + { + typeFlags |= TypeSecurityDescriptorFlags_IsAllTransparent; + } + else if (pModuleSecDesc->IsOpportunisticallyCritical()) + { + // In level 1 transparency, opportunistically critical types are transparent, in level 2 they + // are critical. However, this causes problems when doing type equivalence between levels (for + // instance a type from a v2 PIA which was embedded into a v4 assembly). In order to allow type + // equivalence to work across security rule sets, we consider all types participating in + // equivalence to be transparent under the opportunistically critical rules: + // Participating in equivalence -> Transparent + // Level 1 -> Transparent + // Level 2 -> All critical + if (!pTransparencyBehavior->DoesOpportunisticRequireOnlySafeCriticalMethods() && + !IsTypeEquivalent()) + { + typeFlags |= TypeSecurityDescriptorFlags_IsAllCritical; + } + } + else if (pModuleSecDesc->IsAllCritical()) + { + typeFlags |= TypeSecurityDescriptorFlags_IsAllCritical; + if (pModuleSecDesc->IsTreatAsSafe()) + { + typeFlags |= TypeSecurityDescriptorFlags_IsTreatAsSafe; + } + } + } + + // We need to look at the type token for more information if we still don't know if we're transparent or + // critical. This can also happen if the type is in an opportunistically critical module, however the + // transparency model requires opportunistically critical types to be transparent. In this case, we need + // to make sure that we do not look at the metadata token. + TokenSecurityDescriptor classTokenSecurityDescriptor(m_pMT->GetModule(), + m_pMT->GetCl()); + + const TypeSecurityDescriptorFlags transparencyMask = TypeSecurityDescriptorFlags_IsCritical | + TypeSecurityDescriptorFlags_IsAllCritical | + TypeSecurityDescriptorFlags_IsAllTransparent; + + if (!(typeFlags & transparencyMask) && + !pModuleSecDesc->IsOpportunisticallyCritical()) + { + // First, ask the transparency behavior implementation to map from the metadata attributes to the real + // behavior that we should be seeing. + typeFlags |= pTransparencyBehavior->MapTypeAttributes(classTokenSecurityDescriptor.GetMetadataFlags()); + + // If we still don't know what the transparency of the type is, then we're transparent, but not all + // transparent. That implies that we're in a mixed assembly. + _ASSERTE((typeFlags & transparencyMask) || pModuleSecDesc->IsMixedTransparency()); + } + + // If the transparency behavior dictates that publics must be safe critical, then also set the treat as safe bit. + if (pTransparencyBehavior->DoesPublicImplyTreatAsSafe() && + ((typeFlags & TypeSecurityDescriptorFlags_IsCritical) || (typeFlags & TypeSecurityDescriptorFlags_IsAllCritical)) && + !(typeFlags & TypeSecurityDescriptorFlags_IsTreatAsSafe)) + { + if (IsTypeExternallyVisibleForTransparency()) + { + typeFlags |= TypeSecurityDescriptorFlags_IsTreatAsSafe; + } + } + + // It is common for a v2 assembly to mark a delegate type as explicitly critical rather than all critical, + // since in C# the syntax for creating a delegate type does not make it obvious that a new type is being + // defined. That leads to situations where we commonly have critical types with transparent memebers - + // a nonsense scenario that we reject due to the members not having access to their own this pointer. + // + // For compatibility, we implicitly convert all explicitly critical delegate types into all critical + // types, which is likely what the code intended in the first place, and allows delegate types which + // loaded on v2.0 to continue to load on future runtimes. + // + // Note: While loading BCL classes, we may be running this codepath before it is safe to call MethodTable::IsDelegate. + // That call can only happen after CLASS__MULTICASTDELEGATE has been loaded. However, we should not have any + // explicit critical Delegate types in mscorlib (that can only happen if you're loading v2.0 assembly or have SecurityScope.Explicit). + if ((typeFlags & TypeSecurityDescriptorFlags_IsCritical) && + !(typeFlags & TypeSecurityDescriptorFlags_IsAllCritical) && + m_pMT->IsDelegate()) + { + typeFlags |= TypeSecurityDescriptorFlags_IsAllCritical; + } + + // Update the cached values in the EE Class. + g_IBCLogger.LogEEClassCOWTableAccess(m_pMT); + pClass->SetCriticalTransparentInfo( +#ifndef FEATURE_CORECLR + typeFlags & (TypeSecurityDescriptorFlags_IsCritical | TypeSecurityDescriptorFlags_IsAllCritical), +#endif // FEATURE_CORECLR + typeFlags & TypeSecurityDescriptorFlags_IsTreatAsSafe, + typeFlags & TypeSecurityDescriptorFlags_IsAllTransparent, + typeFlags & TypeSecurityDescriptorFlags_IsAllCritical); +} + +void TypeSecurityDescriptor::ComputeTypeDeclarativeSecurityInfo() +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + } + CONTRACTL_END; + + // if method doesn't have any security return + if (!IsTdHasSecurity(m_pMT->GetAttrClass())) + { + return; + } + + DWORD dwDeclFlags; + HRESULT hr = SecurityDeclarative::GetDeclarationFlags(GetIMDInternalImport(), GetToken(), &dwDeclFlags, NULL, NULL); + + if (SUCCEEDED(hr)) + { + GCX_COOP(); + PsetCacheEntry *tokenSetIndexes[dclMaximumValue + 1]; + SecurityDeclarative::DetectDeclActionsOnToken(GetToken(), dwDeclFlags, tokenSetIndexes, GetIMDInternalImport()); + + // Create single linked list of set indexes + DWORD dwLocalAction; + for (dwLocalAction = 0; dwLocalAction <= dclMaximumValue; dwLocalAction++) + { + if (tokenSetIndexes[dwLocalAction] != NULL) + { + TokenDeclActionInfo::LinkNewDeclAction(&m_pTokenDeclActionInfo, + (CorDeclSecurity)dwLocalAction, + tokenSetIndexes[dwLocalAction]); + } + } + } +} + +BOOL TypeSecurityDescriptor::CanTypeSecurityDescriptorBeCached(MethodTable* pMT) +{ + LIMITED_METHOD_CONTRACT; + + EEClass *pClass = pMT->GetClass(); + return pClass->RequiresLinktimeCheck() || + pClass->RequiresInheritanceCheck() || + // NGEN accesses security descriptors frequently to check for security callouts + IsCompilationProcess(); +} + +BOOL TypeSecurityDescriptor::IsTypeExternallyVisibleForTransparency() +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + PRECONDITION(m_pMT->GetAssembly()->GetSecurityTransparencyBehavior()->DoesPublicImplyTreatAsSafe()); + } + CONTRACTL_END; + + if (m_pMT->IsExternallyVisible()) + { + // If the type is genuinely externally visible, then it is also visible for transparency + return TRUE; + } + else if (m_pMT->IsGlobalClass()) + { + // Global methods are externally visible + return TRUE; + } + else if (m_pMT->IsSharedByGenericInstantiations()) + { + TokenSecurityDescriptor tokenSecDesc(m_pMT->GetModule(), m_pMT->GetCl()); + + // Canonical method tables for shared generic instantiations will appear to us as + // GenericClass<__Canon>, rather than the actual generic type parameter, and since __Canon is not + // public, these method tables will not appear to be public either. + // + // For these types, we'll look at the metadata directly, and ignore generic parameters to see + // if the type is public. Note that this will under-enforce; for instance G<CriticalRefType> will + // have it's G<__Canon> calls refered to as safe critical (which is necessary, since G<__Canon> + // is also the canonical representation for G<TransparentRefType>. We rely on the checks done by + // CheckTransparentAccessToCriticalCode in the CanAccess code path to reject any attempts to use + // the generic type over a critical parameter. + if (tokenSecDesc.IsSemanticExternallyVisible()) + { + return TRUE; + } + } + + return FALSE; +} + +void TypeSecurityDescriptor::VerifyDataComputedInternal() +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + } + CONTRACTL_END; + + if (m_fIsComputed) + { + return; + } + + // If the type hasn't already cached it's transparency information, then we need to calculate it here. It + // can be cached if we're loading the type from a native image, but are creating the security descriptor + // in order to figure out declarative security. + if (!m_pMT->GetClass()->HasCriticalTransparentInfo()) + { + ComputeCriticalTransparentInfo(); + } + + // COMPUTE Type DECLARATIVE SECURITY INFO + ComputeTypeDeclarativeSecurityInfo(); + + // mark computed + InterlockedCompareExchange(reinterpret_cast<LONG *>(&m_fIsComputed), TRUE, FALSE); +} + +void TypeSecurityDescriptor::InvokeInheritanceChecks(MethodTable* pChildMT) +{ + CONTRACTL + { + STANDARD_VM_CHECK; + PRECONDITION(CheckPointer(pChildMT)); + } + CONTRACTL_END; + + const SecurityTransparencyBehavior *pChildTransparencyBehavior = pChildMT->GetAssembly()->GetSecurityTransparencyBehavior(); + if (pChildTransparencyBehavior->AreInheritanceRulesEnforced() && Security::IsTransparencyEnforcementEnabled()) + { + // We compare the child class with the most critical base class in the type hierarchy. + // + // We can stop walking the inheritance chain if we find a type that also enforces inheritance rules, + // since we know that it must be at least as critical as the most critical of all its base types. + // Similarly, we can stop walking when we find a critical parent, because we know that this is the + // most critical we can get. + bool fFoundCriticalParent = false; + bool fFoundSafeCriticalParent = false; + bool fFoundParentWithEnforcedInheritance = false; + + for (MethodTable *pParentMT = m_pMT; + pParentMT != NULL && !fFoundParentWithEnforcedInheritance && !fFoundCriticalParent; + pParentMT = pParentMT->GetParentMethodTable()) + { + EEClass *pParentClass = pParentMT->GetClass(); + + // Make sure this parent class has its transparency information computed + if (!pParentClass->HasCriticalTransparentInfo()) + { + TypeSecurityDescriptor parentSecurityDescriptor(pParentMT); + parentSecurityDescriptor.ComputeCriticalTransparentInfo(); + } + + // See if it is critical or safe critical + if (pParentClass->IsCritical() && pParentClass->IsTreatAsSafe()) + { + fFoundSafeCriticalParent = true; + } + else if (pParentClass->IsCritical() && !pParentClass->IsTreatAsSafe()) + { + fFoundCriticalParent = true; + } + + // If this parent class enforced transparency, we can stop looking at further parents + const SecurityTransparencyBehavior *pParentTransparencyBehavior = pParentMT->GetAssembly()->GetSecurityTransparencyBehavior(); + fFoundParentWithEnforcedInheritance = pParentTransparencyBehavior->AreInheritanceRulesEnforced(); + } + + /* + Allowed Inheritance Patterns + ---------------------------- + + Base Class/Method Derived Class/ Method + ----------------- --------------------- + Transparent Transparent + Transparent SafeCritical + Transparent Critical + SafeCritical SafeCritical + SafeCritical Critical + Critical Critical + + + Disallowed Inheritance patterns + ------------------------------- + + Base Class/Method Derived Class /Method + ----------------- --------------------- + SafeCritical Transparent + Critical Transparent + Critical SafeCritical + */ + + // Make sure the child class has its transparency calculated + EEClass *pChildClass = pChildMT->GetClass(); + if (!pChildClass->HasCriticalTransparentInfo()) + { + TypeSecurityDescriptor childSecurityDescriptor(pChildMT); + childSecurityDescriptor.ComputeCriticalTransparentInfo(); + } + + if (fFoundCriticalParent) + { + if (!pChildClass->IsCritical() || pChildClass->IsTreatAsSafe()) + { +#ifdef _DEBUG + if (g_pConfig->LogTransparencyErrors()) + { + SecurityTransparent::LogTransparencyError(pChildMT, "Transparent or safe critical type deriving from a critical base type"); + } +#endif // _DEBUG + // The parent class is critical, but the child class is not + SecurityTransparent::ThrowTypeLoadException(pChildMT); + } + } + else if (fFoundSafeCriticalParent) + { + if (!pChildClass->IsCritical()) + { +#ifdef _DEBUG + if (g_pConfig->LogTransparencyErrors()) + { + SecurityTransparent::LogTransparencyError(pChildMT, "Transparent type deriving from a safe critical base type"); + } +#endif // _DEBUG + // The parent class is safe critical, but the child class is transparent + SecurityTransparent::ThrowTypeLoadException(pChildMT); + } + } + } + +#ifndef FEATURE_CORECLR + // Fast path check + if (SecurityDeclarative::FullTrustCheckForLinkOrInheritanceDemand(pChildMT->GetAssembly())) + { + return; + } + + if (HasInheritanceDeclarativeSecurity()) + { +#ifdef CROSSGEN_COMPILE + // NGen is always full trust. This path should be unreachable. + CrossGenNotSupported("HasInheritanceDeclarativeSecurity()"); +#else // CROSSGEN_COMPILE + GCX_COOP(); + + // If we have a class that requires inheritance checks, + // then we require a thread to perform the checks. + // We won't have a thread when some of the system classes + // are preloaded, so make sure that none of them have + // inheritance checks. + _ASSERTE(GetThread() != NULL); + + struct + { + OBJECTREF refCasDemands; + OBJECTREF refNonCasDemands; + } + gc; + ZeroMemory(&gc, sizeof(gc)); + + GCPROTECT_BEGIN(gc); + + EEClass *pClass = m_pMT->GetClass(); + if (pClass->RequiresCasInheritanceCheck()) + { + GetDeclaredPermissionsWithCache(dclInheritanceCheck, &gc.refCasDemands, NULL); + } + + if (pClass->RequiresNonCasInheritanceCheck()) + { + GetDeclaredPermissionsWithCache(dclNonCasInheritance, &gc.refNonCasDemands, NULL); + } + + if (gc.refCasDemands != NULL) + { + SecurityStackWalk::LinkOrInheritanceCheck(pChildMT->GetAssembly()->GetSecurityDescriptor(), + gc.refCasDemands, + pChildMT->GetAssembly(), + dclInheritanceCheck); + } + + if (gc.refNonCasDemands != NULL) + { + _ASSERTE(((PERMISSIONSETREF)gc.refNonCasDemands)->CheckedForNonCas() && "Declarative permissions should have been checked for nonCAS in PermissionSet.CreateSerialized"); + if(((PERMISSIONSETREF)gc.refNonCasDemands)->ContainsNonCas()) + { + MethodDescCallSite demand(METHOD__PERMISSION_SET__DEMAND_NON_CAS, &gc.refNonCasDemands); + + ARG_SLOT arg = ObjToArgSlot(gc.refNonCasDemands); + demand.Call(&arg); + } + } + + GCPROTECT_END(); +#endif // CROSSGEN_COMPILE + } +#endif // FEATURE_CORECLR +} + +// Module security descriptor contains static security information about the module +// this information could get persisted in the NGen image +void ModuleSecurityDescriptor::VerifyDataComputed() +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + SO_INTOLERANT; + } + CONTRACTL_END; + + if (m_flags & ModuleSecurityDescriptorFlags_IsComputed) + { + return; + } + +#ifndef FEATURE_CORECLR + ModuleSecurityDescriptorTransparencyEtwEvents etw(this); +#endif // !FEATURE_CORECLR + + // Read the security attributes from the assembly + Assembly *pAssembly = m_pModule->GetAssembly(); + + // Get the metadata flags on the assembly. Note that we cannot use a TokenSecurityDescriptor directly + // here because Reflection.Emit may have overriden the metadata flags with different ones of its own + // choosing. + TokenSecurityDescriptorFlags tokenFlags = GetTokenFlags(); + +#ifdef FEATURE_APTCA + // We need to post-process the APTCA bits on the token security descriptor to handle: + // 1. Conditional APTCA assemblies, which should appear as either APTCA-enabled or APTCA-disabled + // 2. APTCA killbitted assemblies, which should appear as APTCA-disabled + tokenFlags = ProcessAssemblyAptcaFlags(pAssembly->GetDomainAssembly(), tokenFlags); +#endif // FEATURE_APTCA + +#ifndef FEATURE_CORECLR + // Make sure we understand the security rule set being asked for + if (GetSecurityRuleSet() < SecurityRuleSet_Min || GetSecurityRuleSet() > SecurityRuleSet_Max) + { + // Unknown rule set - fail to load this module + SString strAssemblyName; + pAssembly->GetDisplayName(strAssemblyName); + COMPlusThrow(kFileLoadException, IDS_E_UNKNOWN_SECURITY_RULESET, strAssemblyName.GetUnicode()); + } + +#endif // !FEATURE_CORECLR + + // Get a transparency behavior object for the assembly. + const SecurityTransparencyBehavior *pTransparencyBehavior = + SecurityTransparencyBehavior::GetTransparencyBehavior(GetSecurityRuleSet()); + pAssembly->SetSecurityTransparencyBehavior(pTransparencyBehavior); + + ModuleSecurityDescriptorFlags moduleFlags = pTransparencyBehavior->MapModuleAttributes(tokenFlags); + + AssemblySecurityDescriptor *pAssemSecDesc = static_cast<AssemblySecurityDescriptor*>(pAssembly->GetSecurityDescriptor()); + + // We shouldn't be both all transparent and all critical + const ModuleSecurityDescriptorFlags invalidMask = ModuleSecurityDescriptorFlags_IsAllCritical | + ModuleSecurityDescriptorFlags_IsAllTransparent; + if ((moduleFlags & invalidMask) == invalidMask) + { +#ifdef _DEBUG + if (g_pConfig->LogTransparencyErrors()) + { + SecurityTransparent::LogTransparencyError(pAssembly, "Found both critical and transparent assembly level annotations"); + } + if (!g_pConfig->DisableTransparencyEnforcement()) +#endif // _DEBUG + { + COMPlusThrow(kInvalidOperationException, W("InvalidOperation_CriticalTransparentAreMutuallyExclusive")); + } + } + + const ModuleSecurityDescriptorFlags transparencyMask = ModuleSecurityDescriptorFlags_IsAllCritical | + ModuleSecurityDescriptorFlags_IsAllTransparent | + ModuleSecurityDescriptorFlags_IsTreatAsSafe | + ModuleSecurityDescriptorFlags_IsOpportunisticallyCritical; + + // See if the assembly becomes implicitly transparent if loaded in partial trust + if (pTransparencyBehavior->DoesPartialTrustImplyAllTransparent()) + { + if (!pAssemSecDesc->IsFullyTrusted()) + { + moduleFlags &= ~transparencyMask; + moduleFlags |= ModuleSecurityDescriptorFlags_IsAllTransparent; + + moduleFlags |= ModuleSecurityDescriptorFlags_TransparentDueToPartialTrust; + + SString strAssemblyName; + pAssembly->GetDisplayName(strAssemblyName); + LOG((LF_SECURITY, + LL_INFO10, + "Assembly '%S' was loaded in partial trust and was made implicitly all transparent.\n", + strAssemblyName.GetUnicode())); + } + } + + // If the assembly is not allowed to use the SkipVerificationInFullTrust optimization, then disable that bit + if (!pAssembly->GetSecurityDescriptor()->AllowSkipVerificationInFullTrust()) + { + moduleFlags &= ~ModuleSecurityDescriptorFlags_SkipFullTrustVerification; + } + + // Make sure that if the assembly is being loaded in partial trust that it is all transparent. This is a + // change from v2.0 rules, and for compatibility we use the DoesPartialTrustImplyAllTransparent check to + // ensure that v2 assemblies can load in partial trust unmodified. This change does allow us to follow + // the CoreCLR model of using transparency for security enforcement, rather than the v2.0 model of using + // transparency only for audit. + if (!pAssembly->GetSecurityDescriptor()->IsFullyTrusted() && + !(moduleFlags & ModuleSecurityDescriptorFlags_IsAllTransparent)) + { + SString strAssemblyName; + pAssembly->GetDisplayName(strAssemblyName); + +#ifdef _DEBUG + if (g_pConfig->LogTransparencyErrors()) + { + SecurityTransparent::LogTransparencyError(pAssembly, "Attempt to load an assembly which is not fully transparent in partial trust"); + } + if (g_pConfig->DisableTransparencyEnforcement()) + { + SecurityTransparent::LogTransparencyError(pAssembly, "Forcing partial trust assembly to be fully transparent"); + if (!pAssembly->GetSecurityDescriptor()->IsFullyTrusted()) + { + moduleFlags &= ~transparencyMask; + moduleFlags |= ModuleSecurityDescriptorFlags_IsAllTransparent; + + } + } + else +#endif // _DEBUG + { + COMPlusThrow(kFileLoadException, IDS_E_LOAD_CRITICAL_IN_PARTIAL_TRUST, strAssemblyName.GetUnicode()); + } + } + +#ifdef FEATURE_APTCA + // If the security model implies that unsigned assemblies are APTCA, then check to see if we're unsigned + // and set the APTCA bit. + if (pTransparencyBehavior->DoesUnsignedImplyAPTCA() && !pAssembly->IsStrongNamed()) + { + moduleFlags |= ModuleSecurityDescriptorFlags_IsAPTCA; + } +#endif // FEATURE_APTCA + +#ifdef _DEBUG + // If we're being forced to generate native code for this assembly which can be used in a partial trust + // context, then we need to ensure that the assembly is entirely transparent -- otherwise the code may + // perform a critical operation preventing the ngen image from being loaded into partial trust. + if (CLRConfig::GetConfigValue(CLRConfig::INTERNAL_Security_NGenForPartialTrust) != 0) + { + moduleFlags &= ~transparencyMask; + moduleFlags |= ModuleSecurityDescriptorFlags_IsAllTransparent; + } +#endif // _DEBUG + + // Mark the module as having its security state computed + moduleFlags |= ModuleSecurityDescriptorFlags_IsComputed; + InterlockedCompareExchange(reinterpret_cast<LONG *>(&m_flags), + moduleFlags, + ModuleSecurityDescriptorFlags_None); + + // If this assert fires, we ended up racing to different outcomes + _ASSERTE(m_flags == moduleFlags); +} + +#ifndef FEATURE_CORECLR + +// Determine if this assembly was build against a version of the runtime that only supported legacy transparency +BOOL ModuleSecurityDescriptor::AssemblyVersionRequiresLegacyTransparency() +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + SO_INTOLERANT; + } + CONTRACTL_END; + + BOOL fIsLegacyAssembly = FALSE; + + // Check the manifest version number to see if we're a v1 or v2 assembly. We specifically check for the + // manifest version to come back as a string that starts with either v1 or v2; if we get anything + // unexpected, we'll just use the default transparency implementation + LPCSTR szVersion = NULL; + IMDInternalImport *pmdImport = m_pModule->GetAssembly()->GetManifestImport(); + if (SUCCEEDED(pmdImport->GetVersionString(&szVersion))) + { + if (szVersion != NULL && strlen(szVersion) > 2) + { + fIsLegacyAssembly = szVersion[0] == 'v' && + (szVersion[1] == '1' || szVersion[1] == '2'); + } + } + + return fIsLegacyAssembly; +} + +#endif // !FEATURE_CORECLR + +ModuleSecurityDescriptor* ModuleSecurityDescriptor::GetModuleSecurityDescriptor(Assembly *pAssembly) +{ + WRAPPER_NO_CONTRACT; + + Module* pModule = pAssembly->GetManifestModule(); + _ASSERTE(pModule); + + ModuleSecurityDescriptor* pModuleSecurityDesc = pModule->m_pModuleSecurityDescriptor; + _ASSERTE(pModuleSecurityDesc); + + return pModuleSecurityDesc; +} + +#ifdef FEATURE_NATIVE_IMAGE_GENERATION +VOID ModuleSecurityDescriptor::Save(DataImage *image) +{ + STANDARD_VM_CONTRACT; + VerifyDataComputed(); + image->StoreStructure(this, + sizeof(ModuleSecurityDescriptor), + DataImage::ITEM_MODULE_SECDESC); +} + +VOID ModuleSecurityDescriptor::Fixup(DataImage *image) +{ + STANDARD_VM_CONTRACT; + image->FixupPointerField(this, offsetof(ModuleSecurityDescriptor, m_pModule)); +} +#endif + +#if defined(FEATURE_APTCA) || defined(FEATURE_CORESYSTEM) + +//--------------------------------------------------------------------------------------- +// +// Parse an APTCA blob into its corresponding token security descriptor flags. +// + +TokenSecurityDescriptorFlags ParseAptcaAttribute(const BYTE *pbAptcaBlob, DWORD cbAptcaBlob) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + SO_INTOLERANT; + PRECONDITION(CheckPointer(pbAptcaBlob)); + } + CONTRACTL_END; + + TokenSecurityDescriptorFlags aptcaFlags = TokenSecurityDescriptorFlags_None; + + CustomAttributeParser cap(pbAptcaBlob, cbAptcaBlob); + if (SUCCEEDED(cap.SkipProlog())) + { + aptcaFlags |= TokenSecurityDescriptorFlags_APTCA; + + // Look for the PartialTrustVisibilityLevel named argument + CaNamedArg namedArgs[1] = {{0}}; + namedArgs[0].InitI4FieldEnum(g_PartialTrustVisibilityLevel, g_SecurityPartialTrustVisibilityLevel); + + if (SUCCEEDED(ParseKnownCaNamedArgs(cap, namedArgs, _countof(namedArgs)))) + { + // If we have a partial trust visiblity level, then we may additionally be conditionally APTCA. + PartialTrustVisibilityLevel visibilityLevel = static_cast<PartialTrustVisibilityLevel>(namedArgs[0].val.u4); + if (visibilityLevel == PartialTrustVisibilityLevel_NotVisibleByDefault) + { + aptcaFlags |= TokenSecurityDescriptorFlags_ConditionalAPTCA; + } + } + } + + return aptcaFlags; +} + +#endif // defined(FEATURE_APTCA) || defined(FEATURE_CORESYSTEM) + +//--------------------------------------------------------------------------------------- +// +// Parse a security rules attribute blob into its corresponding token security descriptor +// flags. +// + +TokenSecurityDescriptorFlags ParseSecurityRulesAttribute(const BYTE *pbSecurityRulesBlob, + DWORD cbSecurityRulesBlob) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + SO_INTOLERANT; + PRECONDITION(CheckPointer(pbSecurityRulesBlob)); + } + CONTRACTL_END; + + TokenSecurityDescriptorFlags rulesFlags = TokenSecurityDescriptorFlags_None; + + CustomAttributeParser cap(pbSecurityRulesBlob, cbSecurityRulesBlob); + if (SUCCEEDED(cap.SkipProlog())) + { + rulesFlags |= TokenSecurityDescriptorFlags_SecurityRules; + + // Read out the version number + UINT8 bRulesLevel = 0; + if (SUCCEEDED(cap.GetU1(&bRulesLevel))) + { + rulesFlags |= EncodeSecurityRuleSet(static_cast<SecurityRuleSet>(bRulesLevel)); + } + + // See if the attribute specified that full trust transparent code should not be verified + CaNamedArg skipVerificationArg; + skipVerificationArg.InitBoolField("SkipVerificationInFullTrust", FALSE); + if (SUCCEEDED(ParseKnownCaNamedArgs(cap, &skipVerificationArg, 1))) + { + if (skipVerificationArg.val.boolean) + { + rulesFlags |= TokenSecurityDescriptorFlags_SkipFullTrustVerification; + } + } + } + + return rulesFlags; +} + +// grok the meta data and compute the necessary attributes +void TokenSecurityDescriptor::VerifyDataComputed() +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + SO_INTOLERANT; + PRECONDITION(CheckPointer(m_pModule)); + } + CONTRACTL_END; + + if (m_flags & TokenSecurityDescriptorFlags_IsComputed) + { + return; + } + + // Loop over the attributes on the token, reading off bits that are interesting for security + TokenSecurityDescriptorFlags flags = ReadSecurityAttributes(m_pModule->GetMDImport(), m_token); + flags |= TokenSecurityDescriptorFlags_IsComputed; + FastInterlockOr(reinterpret_cast<DWORD *>(&m_flags), flags); +} + +// static +TokenSecurityDescriptorFlags TokenSecurityDescriptor::ReadSecurityAttributes(IMDInternalImport *pmdImport, mdToken token) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + SO_INTOLERANT; + PRECONDITION(CheckPointer(pmdImport)); + } + CONTRACTL_END; + + TokenSecurityDescriptorFlags flags = TokenSecurityDescriptorFlags_None; + + HENUMInternalHolder hEnum(pmdImport); + hEnum.EnumInit(mdtCustomAttribute, token); + + mdCustomAttribute currentAttribute; + while (hEnum.EnumNext(¤tAttribute)) + { + LPCSTR szAttributeName; + LPCSTR szAttributeNamespace; + + if (FAILED(pmdImport->GetNameOfCustomAttribute(currentAttribute, &szAttributeNamespace, &szAttributeName))) + { + continue; + } + + // The only attributes we care about are in System.Security, so move on if we found something in a + // different namespace + if (szAttributeName != NULL && + szAttributeNamespace != NULL && + strcmp(g_SecurityNS, szAttributeNamespace) == 0) + { +#if defined(FEATURE_APTCA) || defined(FEATURE_CORESYSTEM) + if (strcmp(g_SecurityAPTCA + sizeof(g_SecurityNS), szAttributeName) == 0) + { + // Check the visibility parameter + const BYTE *pbAttributeBlob; + ULONG cbAttributeBlob; + + if (FAILED(pmdImport->GetCustomAttributeAsBlob(currentAttribute, reinterpret_cast<const void **>(&pbAttributeBlob), &cbAttributeBlob))) + { + continue; + } + + TokenSecurityDescriptorFlags aptcaFlags = ParseAptcaAttribute(pbAttributeBlob, cbAttributeBlob); + flags |= aptcaFlags; + } + else +#endif // defined(FEATURE_APTCA) || defined(FEATURE_CORESYSTEM) + if (strcmp(g_SecurityCriticalAttribute + sizeof(g_SecurityNS), szAttributeName) == 0) + { + flags |= TokenSecurityDescriptorFlags_Critical; + +#ifndef FEATURE_CORECLR + // Check the SecurityCriticalScope parameter + const BYTE *pbAttributeBlob; + ULONG cbAttributeBlob; + + if (FAILED(pmdImport->GetCustomAttributeAsBlob( + currentAttribute, + reinterpret_cast<const void **>(&pbAttributeBlob), + &cbAttributeBlob))) + { + continue; + } + CustomAttributeParser cap(pbAttributeBlob, cbAttributeBlob); + if (SUCCEEDED(cap.SkipProlog())) + { + UINT32 dwCriticalFlags; + if (SUCCEEDED(cap.GetU4(&dwCriticalFlags))) + { + if (dwCriticalFlags == SecurityCriticalFlags_All) + { + flags |= TokenSecurityDescriptorFlags_AllCritical; + } + } + } +#endif // !FEATURE_CORECLR + } + else if (strcmp(g_SecuritySafeCriticalAttribute + sizeof(g_SecurityNS), szAttributeName) == 0) + { + flags |= TokenSecurityDescriptorFlags_SafeCritical; + } + else if (strcmp(g_SecurityTransparentAttribute + sizeof(g_SecurityNS), szAttributeName) == 0) + { + flags |= TokenSecurityDescriptorFlags_Transparent; + } +#ifndef FEATURE_CORECLR + else if (strcmp(g_SecurityRulesAttribute + sizeof(g_SecurityNS), szAttributeName) == 0) + { + const BYTE *pbAttributeBlob; + ULONG cbAttributeBlob; + + if (FAILED(pmdImport->GetCustomAttributeAsBlob( + currentAttribute, + reinterpret_cast<const void **>(&pbAttributeBlob), + &cbAttributeBlob))) + { + continue; + } + + TokenSecurityDescriptorFlags securityRulesFlags = + ParseSecurityRulesAttribute(pbAttributeBlob, cbAttributeBlob); + + flags |= securityRulesFlags; + } + else if (strcmp(g_SecurityTreatAsSafeAttribute + sizeof(g_SecurityNS), szAttributeName) == 0) + { + flags |= TokenSecurityDescriptorFlags_TreatAsSafe; + } +#endif // !FEATURE_CORECLR + } + } + + return flags; +} + +//--------------------------------------------------------------------------------------- +// +// Calculate the semantic critical / transparent state for this metadata token. +// See code:TokenSecurityDescriptor#TokenSecurityDescriptorSemanticLookup +// + +void TokenSecurityDescriptor::VerifySemanticDataComputed() +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + } + CONTRACTL_END; + + if (m_flags & TokenSecurityDescriptorFlags_IsSemanticComputed) + { + return; + } + +#ifndef FEATURE_CORECLR + TokenSecurityDescriptorTransparencyEtwEvents etw(this); +#endif // !FEATURE_CORECLR + + bool fIsSemanticallyCritical = false; + bool fIsSemanticallyTreatAsSafe = false; + bool fIsSemanticallyExternallyVisible = false; + + // Check the module to see if every type in the module is the same + Assembly *pAssembly = m_pModule->GetAssembly(); + ModuleSecurityDescriptor* pModuleSecDesc = ModuleSecurityDescriptor::GetModuleSecurityDescriptor(pAssembly); + if (pModuleSecDesc->IsAllTransparent()) + { + // If the module is explicitly Transparent, then everything in it is Transparent + fIsSemanticallyCritical = false; + fIsSemanticallyTreatAsSafe = false; + } + else if (pModuleSecDesc->IsAllCritical()) + { + // If the module is critical or safe critical, then everything in it matches + fIsSemanticallyCritical = true; + + if (pModuleSecDesc->IsTreatAsSafe()) + { + fIsSemanticallyTreatAsSafe = true; + } + } + else if (pModuleSecDesc->IsOpportunisticallyCritical()) + { + // There are three cases for an opportunistically critical type: + // 1. Level 2 transparency - all types are critical + // 2. Level 1 transparency - all types are transparent + // 3. Types participating in type equivalence (regardless of level) - types are transparent + // + // Therefore, we consider the type critical only if it is level 2, otherwise keep it transparent. + + const SecurityTransparencyBehavior *pTransparencyBehavior = pAssembly->GetSecurityTransparencyBehavior(); + if (!pTransparencyBehavior->DoesOpportunisticRequireOnlySafeCriticalMethods() && + !IsTypeEquivalent()) + { + // If the module is opportunistically critical, then every type in it is critical + fIsSemanticallyCritical = true; + } + } + // Mixed transparency + else + { + const TypeSecurityDescriptorFlags criticalMask = TypeSecurityDescriptorFlags_IsAllCritical | + TypeSecurityDescriptorFlags_IsCritical; + const TypeSecurityDescriptorFlags treatAsSafeMask = TypeSecurityDescriptorFlags_IsTreatAsSafe; + + const SecurityTransparencyBehavior *pTransparencyBehavior = pAssembly->GetSecurityTransparencyBehavior(); + _ASSERTE(pTransparencyBehavior != NULL); + + // We don't have full module-level state, so we need to loop over the tokens to figure it out. + IMDInternalImport* pMdImport = m_pModule->GetMDImport(); + mdToken tkCurrent = m_token; + mdToken tkPrev = mdTokenNil; + + // First, we need to walk the chain inside out, building up a stack so that we can pop the stack from + // the outside in, looking for the largest scope with a statement about the transparency of the types. + CStackArray<mdToken> typeTokenStack; + while (tkPrev != tkCurrent) + { + typeTokenStack.Push(tkCurrent); + tkPrev = tkCurrent; + IfFailThrow(pMdImport->GetParentToken(tkPrev, &tkCurrent)); + } + + // + // Walk up the chain of containing types, starting with the current metadata token. At each step on the + // chain, keep track of if we've been marked critical / treat as safe yet. + // + // It's important that we use only metadata tokens here, rather than using EEClass and + // TypeSecurityDescriptors, since this method can be called while loading nested types and using + // TypESecurityDescriptor can lead to recursion during type load. + // + // We also need to walk the chain from the outside in, since we listen to the outermost marking. We + // can stop looking at tokens once we found one that has a transparency marking (we've become either + // critical or safe critical), and we've determined that the inner types are not publicly visible. + // + + // We'll start out by saying all tokens are not public if public doesn't imply treat as safe - that + // way we don't flip over to safe critical even if they are all public + bool fAllTokensPublic = pTransparencyBehavior->DoesPublicImplyTreatAsSafe(); + + while (typeTokenStack.Count() > 0 && !fIsSemanticallyCritical) + { + mdToken *ptkCurrentType = typeTokenStack.Pop(); + TokenSecurityDescriptor currentTokenSD(m_pModule, *ptkCurrentType); + + // Check to see if the current type is critical / treat as safe. We only want to check this if we + // haven't already found an outer type that had a transparency attribute; otherwise we would let + // an inner scope have more priority than its containing scope + TypeSecurityDescriptorFlags currentTypeFlags = pTransparencyBehavior->MapTypeAttributes(currentTokenSD.GetMetadataFlags()); + if (!fIsSemanticallyCritical) + { + fIsSemanticallyCritical = !!(currentTypeFlags & criticalMask); + fIsSemanticallyTreatAsSafe |= !!(currentTypeFlags & treatAsSafeMask); + } + + // If the assembly uses a transparency model where publicly visible items are treat as safe, then + // we need to check to see if all the types in the containment chain are visible + if (fAllTokensPublic) + { + DWORD dwTypeAttrs; + IfFailThrow(pMdImport->GetTypeDefProps(tkCurrent, &dwTypeAttrs, NULL)); + + fAllTokensPublic = IsTdPublic(dwTypeAttrs) || + IsTdNestedPublic(dwTypeAttrs) || + IsTdNestedFamily(dwTypeAttrs) || + IsTdNestedFamORAssem(dwTypeAttrs); + } + } + + // If public implies treat as safe, all the types were visible, and we are semantically critical + // then we're actually semantically safe critical + if (fAllTokensPublic) + { + _ASSERTE(pTransparencyBehavior->DoesPublicImplyTreatAsSafe()); + + fIsSemanticallyExternallyVisible = true; + + if (fIsSemanticallyCritical) + { + fIsSemanticallyTreatAsSafe = true; + } + } + } + + // Further, if we're critical due to the assembly, and public implies treat as safe, + // and the outermost nested type is public, then we are safe critical + if (pModuleSecDesc->IsAllCritical() || + pModuleSecDesc->IsOpportunisticallyCritical()) + { + // We shouldn't have determined if we're externally visible or not yet + _ASSERTE(!fIsSemanticallyExternallyVisible); + + const SecurityTransparencyBehavior *pTransparencyBehavior = pAssembly->GetSecurityTransparencyBehavior(); + + if (pTransparencyBehavior->DoesPublicImplyTreatAsSafe() && + fIsSemanticallyCritical && + !fIsSemanticallyTreatAsSafe) + { + IMDInternalImport* pMdImport = m_pModule->GetMDImport(); + mdToken tkCurrent = m_token; + mdToken tkPrev = mdTokenNil; + HRESULT hrIter = S_OK; + + while (SUCCEEDED(hrIter) && tkCurrent != tkPrev) + { + tkPrev = tkCurrent; + hrIter = pMdImport->GetNestedClassProps(tkPrev, &tkCurrent); + + if (!SUCCEEDED(hrIter)) + { + if (hrIter == CLDB_E_RECORD_NOTFOUND) + { + // We don't have a parent class, so use the previous as our outermost + tkCurrent = tkPrev; + } + else + { + ThrowHR(hrIter); + } + } + + DWORD dwOuterTypeAttrs; + IfFailThrow(pMdImport->GetTypeDefProps(tkCurrent, &dwOuterTypeAttrs, NULL)); + if (IsTdPublic(dwOuterTypeAttrs)) + { + fIsSemanticallyExternallyVisible = true; + fIsSemanticallyTreatAsSafe = true; + } + } + } + } + + // Save away the semantic state that we just computed + TokenSecurityDescriptorFlags semanticFlags = TokenSecurityDescriptorFlags_IsSemanticComputed; + if (fIsSemanticallyCritical) + semanticFlags |= TokenSecurityDescriptorFlags_IsSemanticCritical; + if (fIsSemanticallyTreatAsSafe) + semanticFlags |= TokenSecurityDescriptorFlags_IsSemanticTreatAsSafe; + if (fIsSemanticallyExternallyVisible) + semanticFlags |= TokenSecurityDescriptorFlags_IsSemanticExternallyVisible; + + FastInterlockOr(reinterpret_cast<DWORD *>(&m_flags), static_cast<DWORD>(semanticFlags)); +} + +HashDatum TokenSecurityDescriptor::LookupSecurityDescriptor(void* pKey) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + } + CONTRACTL_END; + + HashDatum datum; + AppDomain* pDomain = GetAppDomain(); + + EEPtrHashTable &rCachedMethodPermissionsHash = pDomain->m_pSecContext->m_pCachedMethodPermissionsHash; + + // We need to switch to cooperative GC here. But using GCX_COOP here + // causes 20% perf degrade in some declarative security assert scenario. + // We should fix this one. + CONTRACT_VIOLATION(ModeViolation); + // Fast attempt, that may fail (and return FALSE): + if (!rCachedMethodPermissionsHash.GetValueSpeculative(pKey, &datum)) + { + // Slow call + datum = LookupSecurityDescriptor_Slow(pDomain, pKey, rCachedMethodPermissionsHash); + } + return datum; +} + +HashDatum TokenSecurityDescriptor::LookupSecurityDescriptor_Slow(AppDomain* pDomain, + void* pKey, + EEPtrHashTable &rCachedMethodPermissionsHash ) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + } + CONTRACTL_END; + + HashDatum datum; + SimpleRWLock* prGlobalLock = pDomain->m_pSecContext->m_prCachedMethodPermissionsLock; + // look up the cache in the slow mode + // in the false failure case, we'll recheck the cache anyway + SimpleReadLockHolder readLockHolder(prGlobalLock); + if (rCachedMethodPermissionsHash.GetValue(pKey, &datum)) + { + return datum; + } + return NULL; +} + +HashDatum TokenSecurityDescriptor::InsertSecurityDescriptor(void* pKey, HashDatum pHashDatum) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + } + CONTRACTL_END; + + AppDomain* pDomain = GetAppDomain(); + SimpleRWLock* prGlobalLock = pDomain->m_pSecContext->m_prCachedMethodPermissionsLock; + EEPtrHashTable &rCachedMethodPermissionsHash = pDomain->m_pSecContext->m_pCachedMethodPermissionsHash; + + HashDatum pFoundHashDatum = NULL; + // insert the computed details in our hash table + { + SimpleWriteLockHolder writeLockHolder(prGlobalLock); + // since the hash table doesn't support duplicates by + // default, we need to recheck in case another thread + // added the value during a context switch + if (!rCachedMethodPermissionsHash.GetValue(pKey, &pFoundHashDatum)) + { + // no entry was found + _ASSERTE(pFoundHashDatum == NULL); + // Place the new entry into the hash. + rCachedMethodPermissionsHash.InsertValue(pKey, pHashDatum); + } + } + // return the value found in the lookup, in case there was a duplicate + return pFoundHashDatum; +} |