|
|||||||||||
Managed Controls and Policy Resolution in the .NET Framework
The last few articles discussed the interaction of Security Actions and the Code Access Security permissions. These assembly permissions are based on the Security Policy set up by an administrator. It is important, however, to understand how, at runtime, the application of this policy happens. In this article, we will go a little deeper into this process by showing how application domains use evidence to influence permission grants.
The Demand Security Action checks if the caller of some code has a particular permission. If the caller does not have that permission, code execution can be stopped. What you cannot do is influence what permissions the caller’s code has. Policy grants permission to code based on the interaction of the local security policy set by an administrator, the evidence presented to the policy engine, and a few other factors.
To illustrate this process, we will create a simple control and host it in both a Windows and a Web Forms environment. To make things interesting, we’ll make the control read and write a data file from the local hard drive. This shouldn’t pose a problem in a Windows Form environment, but it will pose a problem in the Web Forms environment. In order to understand how to get the control up and functional in a secure manner, you must first understand how the control receives its permission grant.
Evidence
The journey through policy resolution begins with the Evidence object. Evidence encapsulates characteristics about the assembly like its location, its Strong Name, or it’s Authenticode Signature. Table 1 lists the Evidence objects that ship with the .NET Framework. As previously discussed, this evidence is used to decide which code groups an assembly belongs to, and hence what permissions it is allowed.
Evidence can either be host provided or assembly provided. Host-provided evidence is determined by a managed host (see later in the article). Assembly-provided evidence, on the other hand, is compiled into the assembly and therefore remains static. Strong names are one of the most common forms of assembly provided evidence.
Managed Hosts and Application Domains
Managed applications (including our control) run inside logical units of execution called application domains (AppDomain). The Common Language Runtime provides application domains to allow applications that are running within the same process to be isolated from one another. This permits highly scalable applications to be built by allowing hosts to easily create application domains as needed. Assemblies are loaded into application domains by hosts. Normally, the evidence that is associated with the assembly depends on its identity (i.e. is Zone or URL). The host, however, can override, the assembly’s evidence, and provide its own evidence. Table 2 gives several examples of hosts.
Building the Managed Control and Hosting in a Windows Form
The control is a rich-edit control embedded in a form. The control is colored blue, and you can type text into the control. The control has three functions that can be associated with buttons on a form: to save the text to a file, load the text data from a file, and clear the text from the control. The control is defined in the file control.cs that is in the ControlTest\Control directory. The file build.bat will build the control and copy the resulting assembly to the two directories where the host programs are.
Creating a managed control is a relatively painless process. You need to extend the System.Windows.Forms.Control class and then add your custom functionality. private System.Windows.Forms.RichTextBox tbMain;
This control is instantiated and positioned in the form’s constructor.
this.tbMain = new System.Windows.Forms.RichTextBox(); ... this.tbMain.Location = new System.Drawing.Point(0, 0); this.tbMain.Name = "tbMain"; this.tbMain.Size = new System.Drawing.Size(240, 192); this.tbMain.TabIndex = 0; this.tbMain.Text = ""; this.tbMain.BackColor = Color.Blue;
The control defines methods that clear, load and save the lines. These methods are called by the Windows Forms or ASP.NET application that will host the control.
public void Load() { ... Stream myStream;
OpenFileDialog openFileDialog1 =new OpenFileDialog();
openFileDialog1.InitialDirectory = "c:\\" ; openFileDialog1.Filter = "txt files (*.txt)|*.txt|All files (*.*)|*.*" ; openFileDialog1.FilterIndex = 2 ; openFileDialog1.RestoreDirectory = true ;
if(openFileDialog1.ShowDialog() == DialogResult.OK) { if((myStream = openFileDialog1.OpenFile())!= null) { StreamReader sr = new StreamReader(myStream); tbMain.Text = sr.ReadToEnd(); sr.Close(); myStream.Close(); } } ... }
public void Save() { ... Stream myStream ;
SaveFileDialog saveFileDialog1 = new SaveFileDialog();
saveFileDialog1.Filter = "txt files (*.txt)|*.txt|All files (*.*)|*.*" ; saveFileDialog1.FilterIndex = 2 ; saveFileDialog1.RestoreDirectory = true ;
if(saveFileDialog1.ShowDialog() == DialogResult.OK) { if((myStream = saveFileDialog1.OpenFile())!= null) { StreamWriter sw = new StreamWriter(myStream); sw.Write(tbMain.Text); sw.Close(); myStream.Close(); } } ... }
public void Clear() { tbMain.Text = ""; }
In the ControlTest\Form directory is a simple Windows Form that uses the control and calls these three functions via button controls. Use the build.bat to build the Form. Type in some text. Trying saving the text, clearing the control, and reloading the text again. You should see the identical text you originally typed in. The TestForm application runs in an application domain created by the CLR.
The ASP.NET Host
When you host a managed control in an ASP.NET page, the control will automatically be downloaded to your temporary Internet files folder. After the download is complete, Microsoft Internet Explorer (IE) will automatically perform the task of creating a new application domain for your control, supplying evidence, and loading the control into the new application domain. The control will then be rendered within the ASP.NET page on the client-side. In the ControlTest\Web directory is a simple page that hosts the control and has buttons to exercise the control.
You should use the <object> tag to denote the control you want to render. The id element specifies the instance name of the control class, while the classid element specifies the assembly and class you want to render. You can also assign a height and width to control when it is rendered.
<object id = "test" classid= "Control.dll#TestControl" height= 200 width=200 VIEWASTEXT></object>
Now that we have the control defined, we can call methods on it using the instance name as follows:
<input type = "button" value = "Clear" onclick="test.Clear()"/> <input type = "button" value = "Save" onclick="test.Save()"/> <input type = "button" value = "Load" onclick="test.Load()"/>
Additionally, do the following on the server side to ensure that the control is downloaded by the client:
· Place the dll that contains the in the same directory as the ASP.NET page that will load it. (The build program in the Control directory will do this for you.) · You must web-share this directory according to the instructions in the readme file. If you share this directory as ControlWebTest, access the page with Internet Explorer as http://localhost/ControlWebTest/test.htm. · Make sure that the directory settings of your website specify that code can not be executed. If the directory is set to execute code, then IIS will attempt to execute the control on the server side, causing the control not to render at all on the client-side.
When you run the control from the web page, you will be able to type some text. If you try to save the text however, it will fail because the FileIO permission is lacking. The associated exception is caught by the control’s exception handler. Why does this succeed in one situation and fail in another?
Getting the Control to Run in ASP.NET
After the host has loaded the assembly and determined the relevant evidence, the policy engine consults the default policy of the local machine. Policy defines the rules used to determine the final set of permissions granted to an assembly. For the most part, these rules are defined by the administrator of the machine, but hosts can also define a few of these rules when they create an application domain.
Policy consists of Enterprise, Machine, User, and Application Domain Levels. The first three levels are configurable by administrators while the assembly host sets the last level. The three configurable levels allow administrators to manage policies for organizations, specific machines, or specific users. The application domain level allows developers to programmatically restrict grants specified in the other policy levels.
Each Policy level consists of a hierarchy of code group and permission set objects. Based on the evidence associates with an assembly, it is assigned to a code group. Once an assembly is found to be a member of a code group based on its evidence, it is assigned a permission set associated with that group. For each level, all assigned permissions are unioned together. Next, the preliminary grants of each level are intersected. The final grant is the result of this grant minus any permissions that the assembly specifically refuses.
If you open the NET Framework Configuration Tool you will see a LocalIntranet_Zone Code Group whose associated LocalIntranet Permission Set does not have any FileIO permissions (see Figure 1). The membership condition for this code group is being run from the LocalIntranet zone, which is the case here. So when the grants for all the different policy levels are intersected, the control will lack the FileIO permission grant. When run from the WindowsForm, the associated evidence with the control was the MyComputer zone. The My_Computer_Zone has the FullTrust permission set.
What is the best way to get the control the permission it needs to read and write to the file system on the client? One option might be to grant more permission to either the local intranet or Internet zones. This is a very bad idea because any malicious code from the same zone would have an easier time attacking your system. Another option would be to elevate permissions to code from the URL of the managed control. This is not the safest option either. If you grant more trust to an application based on its URL, then an attacker can spoof the application by placing a malicious assembly with the same name at the same URL.
The safest option is to grant trust based on the strong name of the control. See last month’s article for information on how to sign the control with a strong name. The ControlStrongNameTest directory has a version of the control that has a strong name. Build the control and set up the virtual directory as in the previous example (see the readme file for more information).
To grant higher trust to the control, you should open up the .NET Framework configuration tool that corresponds to the version of the runtime that you are using and create a new code-group on the machine level. Add a membership condition of “Strong Name” and import the key information from either your strong name key file or the control itself. Finally, specify a permission set that is appropriate for your control. Remember, it only needs to have FileIOPermission. You should not give the control full trust, but instead you should give it just the permissions that it needs. In other cases, this might require creating a custom permission set in the configuration tool and assigning the new set to the code group you just created.
Now that you have given the control the permission that it needs, it should run… right? Wrong! If you try to run the control as is, you will notice that nothing happens on the client! You will see no error in IE and the control won’t render (i.e. you will not see the blue background). Your control has generated a security exception and was not allowed to run. See the side bar for information on how to retrieve the error log. At first glance, it appears that we have broken the control by trying to give it more permission.
When IE creates a new application domain, it uses the control’s URL as evidence. After this occurs, the application domain itself becomes only partially trusted. You will recall from past articles that any assembly signed with a strong name has an implicit link demand for full trust placed on it. Because our assembly has a strong name, it performs this link demand against all calling code, which includes the partially trusted application domain created by IE. So, the assembly won’t run because the application domain is failing this link demand.
You can easily remedy this problem by placing the AllowPartiallyTrustedCallersAttribute (APTCA) on the assembly level. This attribute will disable the implicit link demand generated when code signed with a strong name is called. APTCA was discussed in more detail in a previous article. Just uncomment out the APTCA attribute in control.cs and rebuild.
[assembly:AllowPartiallyTrustedCallersAttribute()]
After you place this attribute on the control, you will then see it render on the client side. All should go well until you press the load or save button. You still cannot save a file even though the actual control has the FileIOPermission. What’s wrong this time? When the control attempts to open a stream to the local hard drive, a demand filters up through the control and into the application domain created by IE. Since this application domain does not have the FileIOPermission the file IO still fails. Remember for a demand to succeed, all the callers on the stack need to have the needed permission. We need to assert the FileIOPermission!
You can assert permission for the application domain by creating a new instance of the FileIOPermission object and calling the Assert method. This action will stop the Demand initiated by the FileStream object before it reaches the application domain created by IE. Calling the static RevertAssert method of the FileIOPermission class will deactivate this Assert after the control is done with the FileStream. Place the new calls in the existing Save and Load methods of the control as demonstrated by the following code.
public void Save() { try { new FileIOPermission(PermissionState.Unrestricted) .Assert(); ... } ... finally { FileIOPermission.RevertAssert(); } }
Application Domains and Host Evidence
To illustrate how hosts supply evidence to application, look at the HostControlTest example. This host has two parts, an unmanaged and managed part. Unmanaged C++ is used to host the CLR and start the managed host. The managed host manipulates the evidence that gets passed to an application domain that it creates to start up the Windows Form that uses our control. This division is done for simplicity and ease of exposition since it is easier to demonstrate the use of evidence in managed code. This process is analogous to what an unmanaged host such as Internet Explorer, or the ISAPI filter that starts up ASP.NET must do, when they run managed code such as our control
The code in CLRHost.cpp uses COM interfaces to start up the Common Language Runtime, and launch the managed part of the host. The method CorBindToRuntimeEx, which is exported by mscorlib.dll, returns an ICorRuntimeHost COM interface pointer that can be used to interact with the CLR. For example, the Start and Stop methods on that interface start and stop the CLR instance from running.
HRESULT hr = CorBindToRuntimeEx(NULL, bstrCLRType, STARTUP_LOADER_OPTIMIZATION_MULTI_DOMAIN | STARTUP_CONCURRENT_GC, CLSID_CorRuntimeHost, IID_ICorRuntimeHost, (void **)&pHost);
The assembly containing the managed part of the host is then loaded into the default domain, and an instance of its ManagedHost class is created. The StartManagedHost method of that class is then invoked.Within that method, a new application domain is created with host modifying the evidence associated with that domain.
The first thing the managed host does is to get whatever evidence was associated with the default domain. The managed host then creates a new Evidence object with the existing evidence, and adds to it some Zone Evidence. This evidence is associated with a new application domain. This application then launches our TestForm.exe application.
Evidence currentEvidence = AppDomain.CurrentDomain.Evidence; Evidence ev = new Evidence(currentEvidence);
// Zone zone = new Zone(SecurityZone.Intranet); Zone zone = new Zone(SecurityZone.MyComputer);
ev.AddHost(zone);
AppDomain newDomain = AppDomain.CreateDomain("FormTest", ev); int val = newDomain.ExecuteAssembly("TestForm.exe");
Whether this application runs or not depends on the nature of the evidence that the managed host supplies. If the Zone evidence is MyComputer, the code will have FullTrust rights and will run. If the Zone evidence is Intranet, then application will not be able to run. Just comment and uncomment out the appropriate lines to create the appropriate Zone evidence class. Note that the host could associate the Intranet zone evidence with the application domain even though the application was not being run through IE, or through a URL.
Passing your own evidence is useful for situations where you need to download and run code from a local drive. Usually, code executed from a local drive will receive full trust. However, if your application downloads code from the internet, you may not want it to have full trust, because, after all, the code is really from the Internet. Creating an application domain and passing your own evidence allows you to limit the permissions that any downloaded code receives.
Final Thoughts
Here is a checklist of things to keep track of when hosting controls in Internet Explorer:
The user control assembly needs a strong name and the AllowPartiallyTrustedCallers attribute. Of course, you have to make sure that there are no security problems with your assembly when you apply this attribute. We discussed this attribute in the February issue.
The client has a code group giving the required permissions to assemblies signed with the strong name used to sign the user control.
The user control asserts permissions it requires which Internet Explorer would not normally be granted. The user control RevertAsserts immediately after performing asserted actions. Using an assert means you trust all your callers.
The user control is hosted in an IIS folder on the server that has an "execute permission" of either "none" or scripts only".
By the time this article appears Microsoft may have released the next version of the .NET Framework and you may be running two versions of the CLR “side-by-side”. Make sure you work with the Configuration utility for the version of the runtime you are using.
Both application domains and assemblies have their capabilities dictated by the permission grant process. A good understanding of how they interact with evidence and the local security policy is vital to make most hosting scenarios work. Hosting a managed control in Internet Explorer is a prime example.
When setting up your own application domain or using and existing host like IE, always keep in mind the principle of least privilege. You should only allow an assembly to be granted just enough permissions to do its job, but no more.
Michael Stiefel is the principal of Reliable Software, Inc. where he does training and consulting in Microsoft technologies for companies ranging from startups to the Fortune 500. He is the co-author of “Application Development Using C# and .NET” published by Prentice-Hall. Go to www.reliablesoftware.com for more information about his development work and the training courses he offers.
Stephen McCloskey is a Programmer/Writer for Microsoft Corporation. He has worked on Version 1.0 and 1.1 of the .NET Framework, where he wrote security-related documentation and samples for the .NET Framework SDK and MSDN. He continues to write security-related material for future Microsoft products.
|
|||||||||||||||||||||||||||||||
All Content (c) 2000 - 2014 Reliable Software, Inc. All rights reserved. |