diff --git a/src/coreclr/tools/ILVerification.Tests/ILTests/AccessTestsIgnoreChecks.il b/src/coreclr/tools/ILVerification.Tests/ILTests/AccessTestsIgnoreChecks.il
new file mode 100644
index 00000000000000..424476f0e315a4
--- /dev/null
+++ b/src/coreclr/tools/ILVerification.Tests/ILTests/AccessTestsIgnoreChecks.il
@@ -0,0 +1,109 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+.assembly extern System.Runtime
+{
+}
+
+.assembly extern AccessTests
+{
+}
+
+.assembly AccessTestsIgnoreChecks
+{
+ // IgnoresAccessChecksTo("AccessTests")
+ .custom instance void System.Runtime.CompilerServices.IgnoresAccessChecksToAttribute::.ctor(string) = (
+ 01 00 0b 41 63 63 65 73 73 54 65 73 74 73 00 00
+ )
+}
+
+// The IgnoresAccessChecksToAttribute is not part of the BCL surface area; consumers of the
+// feature (e.g. the IgnoresAccessChecksToGenerator) declare it in their own assembly. The
+// runtime and ilverify recognize it purely by its namespace and name.
+.class private auto ansi sealed beforefieldinit System.Runtime.CompilerServices.IgnoresAccessChecksToAttribute
+ extends [System.Runtime]System.Attribute
+{
+ .method public hidebysig specialname rtspecialname instance void .ctor(string assemblyName) cil managed
+ {
+ ldarg.0
+ call instance void [System.Runtime]System.Attribute::.ctor()
+ ret
+ }
+}
+
+.class public auto ansi beforefieldinit AccessTestsIgnoreChecksType
+ extends [System.Runtime]System.Object
+{
+ .method public hidebysig instance void Instantiate.PrivateClass_Valid() cil managed
+ {
+ newobj instance void [AccessTests]PrivateClass::.ctor()
+ pop
+ ret
+ }
+
+ .method public hidebysig instance void Instantiate.PrivateNested_Valid() cil managed
+ {
+ newobj instance void [AccessTests]SimpleClass/PrivateNestedClass::.ctor()
+ pop
+ ret
+ }
+
+ .method public hidebysig instance void Instantiate.AssemblyNested_Valid() cil managed
+ {
+ newobj instance void [AccessTests]SimpleClass/AssemblyNestedClass::.ctor()
+ pop
+ ret
+ }
+
+ .method public hidebysig instance void Instantiate.FamilyNested_Valid() cil managed
+ {
+ newobj instance void [AccessTests]SimpleClass/FamilyNestedClass::.ctor()
+ pop
+ ret
+ }
+
+ .method public hidebysig instance void Call.PrivateMethod_Valid() cil managed
+ {
+ call void [AccessTests]SimpleClass::PrivateMethod()
+ ret
+ }
+
+ .method public hidebysig instance void Call.FamilyMethod_Valid() cil managed
+ {
+ call void [AccessTests]SimpleClass::FamilyMethod()
+ ret
+ }
+
+ .method public hidebysig instance void Call.AssemblyMethod_Valid() cil managed
+ {
+ call void [AccessTests]SimpleClass::AssemblyMethod()
+ ret
+ }
+
+ .method public hidebysig instance void Call.FamAndAssemMethod_Valid() cil managed
+ {
+ call void [AccessTests]SimpleClass::FamilyAndAssemblyMethod()
+ ret
+ }
+
+ .method public hidebysig instance void Load.PrivateField_Valid() cil managed
+ {
+ ldsfld int32 [AccessTests]SimpleClass::privateField
+ pop
+ ret
+ }
+
+ .method public hidebysig instance void Load.AssemblyField_Valid() cil managed
+ {
+ ldsfld int32 [AccessTests]SimpleClass::assemblyField
+ pop
+ ret
+ }
+
+ .method public hidebysig instance void Load.PublicFieldOfPrivateClass_Valid() cil managed
+ {
+ ldsfld int32 [AccessTests]SimpleClass/PrivateNestedClass::publicField
+ pop
+ ret
+ }
+}
diff --git a/src/coreclr/tools/ILVerification.Tests/ILTests/AccessTestsIgnoreChecks.ilproj b/src/coreclr/tools/ILVerification.Tests/ILTests/AccessTestsIgnoreChecks.ilproj
new file mode 100644
index 00000000000000..356b4dcc778989
--- /dev/null
+++ b/src/coreclr/tools/ILVerification.Tests/ILTests/AccessTestsIgnoreChecks.ilproj
@@ -0,0 +1,9 @@
+
+
+ $(MSBuildProjectName)
+
+
+
+
+
+
diff --git a/src/coreclr/tools/ILVerification/AccessVerificationHelpers.cs b/src/coreclr/tools/ILVerification/AccessVerificationHelpers.cs
index 81cc18f0c7780a..9825e0b3f5d4b6 100644
--- a/src/coreclr/tools/ILVerification/AccessVerificationHelpers.cs
+++ b/src/coreclr/tools/ILVerification/AccessVerificationHelpers.cs
@@ -37,6 +37,10 @@ internal static bool CanAccess(this TypeDesc currentClass, TypeDesc targetClass)
var currentTypeDef = (MetadataType)currentClass.GetTypeDefinition();
var targetTypeDef = (EcmaType)targetClass.GetTypeDefinition();
+ // The accessing assembly can waive all access checks to the target assembly via [IgnoresAccessChecksTo].
+ if (currentTypeDef.Module.IgnoresAccessChecksTo(targetTypeDef.Module))
+ return true;
+
var targetContainingType = targetTypeDef.ContainingType;
if (targetContainingType == null)
{
@@ -110,6 +114,10 @@ private static bool CanAccessMember(this MetadataType currentType, TypeDesc targ
if (memberVisibility == MethodAttributes.Public)
return true;
+ // The accessing assembly can waive all access checks to the target assembly via [IgnoresAccessChecksTo].
+ if (currentType.Module.IgnoresAccessChecksTo(targetTypeDef.Module))
+ return true;
+
// This is module-scope checking, to support C++ file & function statics.
if (memberVisibility == MethodAttributes.PrivateScope)
return currentType.Module == targetTypeDef.Module;
@@ -274,6 +282,30 @@ static ReadOnlySpan GetPublicKeyToken(AssemblyNameInfo assemblyName)
return false;
}
+ private static bool IgnoresAccessChecksTo(this ModuleDesc accessingModule, ModuleDesc accessedModule)
+ {
+ var accessingAssembly = accessingModule.ToEcmaAssembly();
+ var accessedAssembly = accessedModule.ToEcmaAssembly();
+ if (accessingAssembly == null || accessedAssembly == null || accessingAssembly == accessedAssembly)
+ {
+ return false;
+ }
+
+ var accessedName = accessedAssembly.GetName();
+
+ foreach (var attribute in accessingAssembly.GetDecodedCustomAttributes("System.Runtime.CompilerServices", "IgnoresAccessChecksToAttribute"))
+ {
+ // The attribute argument is an assembly name; it is typically just the simple name.
+ // Matching on the simple name mirrors the runtime, which matches by name when the
+ // declared name omits a public key token (see FriendAssemblyDescriptor::IsAssemblyOnList).
+ AssemblyNameInfo ignoredName = AssemblyNameInfo.Parse(((string)attribute.FixedArguments[0].Value).AsSpan());
+ if (accessedName.Name.Equals(ignoredName.Name, StringComparison.OrdinalIgnoreCase))
+ return true;
+ }
+
+ return false;
+ }
+
private static MethodAttributes NestedToMethodAccessAttribute(TypeAttributes nestedVisibility)
{
switch (nestedVisibility & TypeAttributes.VisibilityMask)