File: src\Framework\MS\Internal\AppModel\AssemblyFilter.cs
Project: wpf\PresentationFramework.csproj (PresentationFramework)
//---------------------------------------------------------------------------
//
// <copyright file="SecurityMgrSite.cs" company="Microsoft">
//    Copyright (C) Microsoft Corporation.  All rights reserved.
// </copyright>
// 
// Description:
//              This class is used to filter assemblies as they are loaded into an application domain.
//              The intent is to bring the AppDomain down in the case that one of these is on a disallowed list
//              similar to the kill bit for Activex
//
// History:
//  11/15/05: akaza     Created. 
//---------------------------------------------------------------------------
 
using System;
using System.Windows;
using MS.Internal.PresentationFramework;
using System.Collections.Generic;
using MS.Win32;
using Microsoft.Win32;
using System.Security;
using System.Security.Permissions;
using System.Reflection;
using System.Text;
using MS.Internal.AppModel;
using MS.Internal;
using System.Windows.Resources;
using System.Diagnostics;
using System.Runtime.InteropServices;
 
 
namespace MS.Internal 
{
 
    internal class AssemblyFilter
    {
        /// <SecurityNote>
        ///     Critical: This code sets the allowed assemblies on AssemblyList 
        ///     TreatAsSafe: Initializing the data is ok since it does not expose anything
        /// </SecurityNote>
        [SecurityCritical,SecurityTreatAsSafe]
        static  AssemblyFilter()
        {
            _disallowedListExtracted = new SecurityCriticalDataForSet<bool>(false);
            _assemblyList = new SecurityCriticalDataForSet<System.Collections.Generic.List<string>>(new System.Collections.Generic.List<string>());
        }
 
        /// <SecurityNote>
        ///     Critical: This code calls into unmanaged Api that has a SUC on this (IAssemblCache related)
        /// </SecurityNote>
        [SecurityCritical]
        internal void FilterCallback(Object sender, AssemblyLoadEventArgs args)
        {
            // This code is reentrant
            lock (_lock)
            {
                // Extract assembly
                Assembly a = args.LoadedAssembly;
                // xmlns cache loads assemblies as reflection only and we cannot inspect these using the code below
                // so we ignore also keeping this first is super important because the first time cost is really high
                // other wise also we cannot do any processing on a reflection only assembly aside from reflection based actions
                if (!a.ReflectionOnly)
                {
                    // check if it is in the Gac , this ensures that we eliminate any non GAC assembly which are of no risk
                    if (a.GlobalAssemblyCache)
                    {
                        object[] aptca = a.GetCustomAttributes(typeof(AllowPartiallyTrustedCallersAttribute), false);
                        // if the dll has APTCA
                        if (aptca.Length > 0 && aptca[0] is AllowPartiallyTrustedCallersAttribute)
                        {
                            string assemblyName = AssemblyNameWithFileVersion(a);
                            // If we are on the disallowed list kill the application domain
                            if (AssemblyOnDisallowedList(assemblyName))
                            {
                                // Kill the application domain
                                UnsafeNativeMethods.ProcessUnhandledException_DLL(SR.Get(SRID.KillBitEnforcedShutdown) + assemblyName);
                                // I want to ensure that the process really dies
                                new SecurityPermission(SecurityPermissionFlag.UnmanagedCode).Assert();//BlessedAssert
                                try
                                {
                                    System.Environment.Exit(-1);
                                }
                                finally
                                {
                                    SecurityPermission.RevertAssert();
                                    Debug.Fail("Environment.Exit() failed.");
                                }
                            }
                        }
                    }
                }
            }
        }
 
        //appends assembly name with file version to generate a unique entry for the assembly lookup process
        /// <SecurityNote>
        ///     Critical: This code elevates to extract assembly name
        /// </SecurityNote>
        [SecurityCritical]
        private string AssemblyNameWithFileVersion(Assembly a)
        {
            FileVersionInfo fileVersionInfo;
            StringBuilder sb = new StringBuilder(a.FullName);
            // we need unrestricted here because the location is demands too.
            (new FileIOPermission(PermissionState.Unrestricted)).Assert();//BlessedAssert
            try
            {
                fileVersionInfo = FileVersionInfo.GetVersionInfo(a.Location);
            }
            finally
            {
                FileIOPermission.RevertAssert();
            }
            if (fileVersionInfo != null && fileVersionInfo.ProductVersion != null)
            {
                sb.Append(FILEVERSION_STRING + fileVersionInfo.ProductVersion);
            }
            return ((sb.ToString()).ToLower(System.Globalization.CultureInfo.InvariantCulture)).Trim();
        }
 
        /// <SecurityNote>
        ///     Critical: This code populates _assemblyList with Disallowed Elements and sets the bit that dictates whether to repopulate it
        /// </SecurityNote>
        [SecurityCritical]
        private bool AssemblyOnDisallowedList(String assemblyToCheck)
        {
            bool retVal = false;
            // if the list disallowed list is not populated populate it once
            if (_disallowedListExtracted.Value == false)
            {
                // hit the registry one time and read 
                ExtractDisallowedRegistryList();
                _disallowedListExtracted.Value = true;
            }
            if (_assemblyList.Value.Contains(assemblyToCheck))
            {
                retVal = true;
            }
            return retVal;
        }
 
        /// <SecurityNote>
        ///     Critical: This code opens an HKLM registry location and reads it. We do not want 
        ///     to call this over and over as it could cause performance issues
        /// </SecurityNote>
        [SecurityCritical]
        private  void ExtractDisallowedRegistryList()
        {
            string[] disallowedAssemblies;
            RegistryKey featureKey;
            //Assert for read access to HKLM\Software\Microsoft\.NetFramework\Policy\APTCA
            (new RegistryPermission(RegistryPermissionAccess.Read, KILL_BIT_REGISTRY_HIVE + KILL_BIT_REGISTRY_LOCATION)).Assert();//BlessedAssert
            try
            {
                // open the key and read the value
                featureKey = Registry.LocalMachine.OpenSubKey(KILL_BIT_REGISTRY_LOCATION);
                if (featureKey != null)
                {
                    // Enumerate through all keys and populate dictionary
                    disallowedAssemblies = featureKey.GetSubKeyNames();
                    // iterate over this list and for each extract the APTCA_FLAG value and set it in the 
                    // dictionary
                    foreach (string assemblyName in disallowedAssemblies)
                    {
                        featureKey = Registry.LocalMachine.OpenSubKey(KILL_BIT_REGISTRY_LOCATION + @"\" + assemblyName);
                        object keyValue = featureKey.GetValue(SUBKEY_VALUE);
                        // if there exists a value and it is 1 add to hash table
                        if ((keyValue != null) && (int)(keyValue) == 1)
                        {
                            if (!_assemblyList.Value.Contains(assemblyName))
                            {
                                _assemblyList.Value.Add(assemblyName.ToLower(System.Globalization.CultureInfo.InvariantCulture).Trim());
                            }
                        }
                    }
 
                }
            }
            finally
            {
                RegistryPermission.RevertAssert();
            }
        }
 
        /// <SecurityNote>
        ///     Critical: This holds a list of assemblies that are on an allowed and disallowed list and can be exploited to load
        ///     unsafe dll's into appdomain
        /// </SecurityNote>
        static SecurityCriticalDataForSet<System.Collections.Generic.List<string>> _assemblyList;
 
        /// <SecurityNote>
        ///     Critical: This bit determines whether we need to hit the registry and load the disallowed elements.
        ///     We would like to see this happen only once per appdomain and delay it as much as possible
        /// </SecurityNote>
        static SecurityCriticalDataForSet<bool> _disallowedListExtracted;
 
        static object _lock = new object();
 
        private const string FILEVERSION_STRING = @", FileVersion=";
        // This is the location in the registry where all the keys are stored
        private const string KILL_BIT_REGISTRY_HIVE = @"HKEY_LOCAL_MACHINE\";
        private const string KILL_BIT_REGISTRY_LOCATION = @"Software\Microsoft\.NetFramework\policy\APTCA";
        private const string SUBKEY_VALUE = @"APTCA_FLAG";
    }
 
}