Handling Custom Zones with the HostSecurityManager

We've looked at how the CLR supports mapping a custom zone to the Internet zone and how you can modify the SSCLI to handle whatever zone mapping policy you want, but if you want richer mapping than is built in and don't want to run on a custom SSCLI build you have a third option.  For instance, if your enterprise defines two custom zones conveniently named HighTrustCustomZone and LowTrustCustomZone you might want to map the former to the MyComputer evidence and the latter to Internet evidence.

The HostSecurityManager has a ProvideAssemblyEvidence method which is called when an assembly is loaded and allows the host to modify the evidence which will be assigned to an assembly however it wants.  This looks like a great place to add a richer custom zone mapping.

Lets start with the ADMHost sample application and build this functionality up.  The first step is to enable ADMHost to resolve a URL into a zone.  This is easily done with the code from last week (tweaked a bit to take an LPWSTR rather than an std::wstring).  This can be added to the CClrHost class in ADMHost, and exposed as a method of the IUnmanagedHost interface.  (Note that this will also require adding urlmon.lib to the libraries that ADMHost.exe links against.)  With that in place, our managed host can now easily determine the zone that an assembly belongs to.

We now need to override the HostSecurityManager property of the ManagedHost class, providing an instance of a custom HostSecurityManager -- lets call it AdmHostSecurityManager.  While we're modifying the ManagedHost class, lets also expose the IUnmanagedHost via a property

        private AdmHostSecurityManager securityManager = new AdmHostSecurityManager();
        private IUnmanagedHost unmanagedHost = null;

        /// <summary>
        /// Get the host security manager for this domain
        /// </summary>
        public override HostSecurityManager HostSecurityManager
        {
            get { return securityManager; }
        }

        /// <summary>
        /// Unmanaged half of the CLR host
        /// </summary>
        internal IUnmanagedHost UnmanagedHost
        {
            get { return unmanagedHost; }
        }

Now that the CLR knows how to find our custom HostSecurityManager, we'll need to override the ProvideAssemblyEvidence method.  The logic in this method is pretty straight forward:

  1. Remove any Zone evidence the CLR already applied
  2. Loop through the remaining evidence looking for any Url evidence
  3. Map each URL to a zone
  4. If the zone is one of the standard ones, map it to itself; if it's one of our custom zones provide the mapping we want; otherwise map it to NoZone.
  5. Merge the newly created Zone evidence back into the evidence for the assembly

The code will end up looking like this:

        /// <summary>
        /// Operations the host security manager will be involved in
        /// </summary>
        public override HostSecurityManagerOptions Flags
        {
            get { return HostSecurityManagerOptions.HostAssemblyEvidence; }
        }

        /// <summary>
        /// Update the evidence of an assembly
        /// </summary>
        public override Evidence ProvideAssemblyEvidence(Assembly loadedAssembly, Evidence inputEvidence)
        {
            // get a pointer to the unmanaged host
            Debug.Assert(AppDomain.CurrentDomain.DomainManager != null, "No AppDomainManager present");
            Debug.Assert(AppDomain.CurrentDomain.DomainManager is ManagedHost, "Incorrect AppDomainManager");
            IUnmanagedHost unmanagedHost = (AppDomain.CurrentDomain.DomainManager as ManagedHost).UnmanagedHost;

            // We need the native half of our host setup to do the mapping
            if (unmanagedHost == null)
                return inputEvidence;

            // get rid of any automatically assigned zone evidence
            inputEvidence.RemoveType(typeof(Zone));

            // provide the mapping ourselves
            List<Zone> zoneMappings = new List<Zone>();
            foreach (object evidence in inputEvidence)
            {
                Url url = evidence as Url;
                if (url != null)
                {
                    int zone = unmanagedHost.MapUrlToZone(url.Value);
                    
                    // if this is a well known zone, then just pass it through
                    if (zone <= (int)SecurityZone.MyComputer || (int)SecurityZone.Untrusted <= zone)
                        zoneMappings.Add(new Zone((SecurityZone)zone));
                    else if (zone == HighTrustCustomZone)
                        zoneMappings.Add(new Zone(SecurityZone.MyComputer));
                    else if (zone == LowTrustCustomZone)
                        zoneMappings.Add(new Zone(SecurityZone.Internet));
                    else
                        zoneMappings.Add(new Zone(SecurityZone.NoZone));
                }
            }

            // merge the custom zone mappings back into the evidence
            foreach (Zone zone in zoneMappings)
                inputEvidence.AddHost(zone);

            return inputEvidence;
        }

Another route that you could have chosen to go here is to create a custom evidence class, and use that class to represent the custom zones that you're interested in.

Very similar code could be used to provide other filterings too.  For instance, if you're not interested in custom zones, but you do want to filter out any MyComputer zone evidence you could easily modify the above code to accomplish that.