PermitOnly is excellent for security, but can be a bane to programming..., enter PermissionSet
Doing a PermitOnly when using File Permissions seems to be the way to lock down file access in the .NET environment. It ensures that the API you are calling can only access the specified file path and often times this can be important unless you trust the library you are calling (I try not to trust any code, including MS supplied code, since if they do have a security hole, I'd rather not be the one to accidentally find it). It becomes even more important when someone else you can't trust is using your code, namely the user.
So let us set up a simple scenario. I just wrote a special class library, WebCrawler, that is capable of surfing the web, navigating links, providing callbacks when content is available, and saving out files that I happen to think might be important. WebCrawler tries to preserve the page hierarchy of any pages I save by also saving out the images, and thus the security hole begins to open. Also, WebCrawler isn't necessarily written by me and it contains no file restrictions internally. So how do I securely work with this application?
Well, the biggest security risk is the Save method. So what we'll want to do is limit file access whenever we call that method. I can do this using a standard PermitOnly check which I've covered in a previous blog entry (http://weblogs.asp.net/justin_rogers/archive/2004/01/19/60435.aspx) on the subject. However, the WebCrawler library can't exist under a PermitOnly environment. Why? Because it also requires web permissions and possibly other permissions that I'm disallowing it by only allowing it the single file access permission.
Rather than lose the ability to use permit only I'm going to create a PermissionSet instead. Normally, this might not be obvious to most users. You can't get a PermissionSet that represents the current set of active permissions. However, you can create a new unrestricted permission set. This new permission set is not really unrestricted, it simply means that no new restrictions on top of the currently set restrictions are going to be applied.
PermissionSet pSet = new PermissionSet(PermissionState.Unrestricted);
With that in mind, I could set the above permission set and do a PermitOnly and all code would run as would normally run had I not done the PermitOnly. But we wanted limited file access. So now, we can add our file permission to the mix.
string contentDirectory = GetSafeContentDirectory(); // Still a security item here. Make sure the
// user doesn't screw themselves in setting a directory. I recommend making sure the directory
// exists in the Application Data section of the user's disk, underneath of a directory identifying
// your application.
pSet.AddPermission(new FileIOPermission(FileIOPermissionAccess.Write, contentDirectory));
Now, when we do our PermitOnly, the called code will be able to do anything it would normally do, minus the file restrictions we put into place. This is actually pretty powerful, since we've only added a couple of lines of code and can now *safely* call our untrusted library while giving it all of the permissions it might want and restricting it's access to the disk. You can be even more safe with this routine by creating an limited rather than unrestricted permission set.
PermissionSet pSet = new PermissionSet(PermissionState.None);
pSet.AddPermission(new FileIOPermission(FileIOPermissionAccess.Write, contentDirectory));
pSet.AddPermission(new WebPermission(PermissionState.Unrestricted));
The above, will make sure they can access the web, and some files, but nothing else. You get onto some slippery slopes with code like the above, because you might define permission sets where the library can't do things like multi-threading (this is prominent in web spidering applications) or other operations it might expect. You'd have to do extensive testing with the library while slowly building up your permission sets to make sure unexpected errors don't occur.
The last thing is to make sure you are correctly setting up the permissions using PermitOnly. You almost always want to protect your code against exceptions that might limit the ability of your code to run. Here is a short example:
FileIOPermission ioPerm = new FileIOPermission(FileIOPermissionAccess.Write, contentDirectory));
ioPerm.PermitOnly();
webCrawler.Save(saveFile);
// Back to my code, this will fail.
ReadNewlySavedFile(saveFile);
You can make this work with some exception catching, and you can use this to make sure you've defined the appropriate permissions for the library you are calling.
try {
ioPerm.PermitOnly();
webCrawler.Save();
} catch(SecurityException exc) {
// Write some code to spit this out to disk or the EventLog
} finally {
// Reset your permissions
SecurityPermission.RevertPermitOnly();
}
ReadNewlySavedFile(saveFile); // Even here, I would re-define my permissions rather than just using this method.
Well, it looks like MS thought of everything at this point. I'm not too worried about my code causing a security hole because they've given me all of the tools that I need in order to be a security oriented developer as long as I think securely when I write my applications. I'll talk about how you can do even more with the security system so you don't even have to think all that securely. Some of the declaritive attributes you can easily add to your code to disable access to everything except what you need can come in handy and prevent the spaghetti code you might end up writing when thinking securely.