The Basics of Manipulating File Access Control Lists with C#

A familiar security saying goes: "A system is as secure as its weakest link." For example, you might implement full-blown, high-level security features in your application, but if somebody could simply access the physical database file directly, all the work you've spent on developing security might be in vain.

One of the fundamental security best practices is that you shouldn't rely on a single, big security barrier alone. Instead, it is often wiser to build security on different layers, and the previously mentioned database security is a great example. Unless you secure the database file itself, your application cannot be considered very secure.

Especially in complex IT environments, figuring correct file level security settings can be a tough job. Furthermore, actually applying and maintaining those settings can be time consuming. But with automation, the administrator can save hours and hours of work—and automation is something developers enjoy doing. Before checking what C# and the .NET Framework can do to help you secure your file systems, see what basic security options Windows gives.

File Access Control Lists

Especially with networked file servers and their file shares, you might have run into the Windows error message "Access denied." Although file shares also have their own security settings, most probably these errors result because somebody, likely the administrator, has restricted the user's access to certain files or folders.

In Windows, the NTFS file system supports file-level security settings; FAT-based file systems are out of luck. Simply put, these settings allow the creator of a given file (or the administrators) to specify who can access that file, and what can be done with it. For example, an administration might set file security so that Marketing can access certain files, but Sales cannot. This is called discretionary access control.

Figure 1 shows an example of security settings associated with a folder in Windows. With the Security tab, users with proper rights can precisely control in what way users and groups can access, modify, and delete files and folders. In Windows, these file-level security settings are one of the most low-level security settings that can be made. This means that you should build other security-related features on top of file-level security.

Figure 1: The Security tab in the Windows folder properties shows the access control entries associated with the object.

Technically speaking, file-level security settings are stored in so-called access control lists, or ACLs for short. Each ACL can contain zero or more (yes, and empty ACL is considered valid) access control entries, or ACEs. An ACE contains a security identifier (or SID) of a user or a group, entry type (access allowed or access denied), and a mask specifying which operations are either allowed or denied, depending on the entry type.

For example, a typical ACE might say that modifying a certain file is allowed for Jane, but denied from Peter. Although simple in concept, in the real world setting up access control lists isn't exactly as simple because you can make access control inheritable from parent directories. Similarly, the exact order of access control entries in the list is important. Luckily, the .NET Framework takes reasonably good care of the developer and modifying file security is much easier than it used to be when using the older Win32 API directly.

An Example of .NET's Support for File Access Control

When the .NET Framework 2.0 came along, it contained support for file access control lists built-in. In previous framework versions (namely 1.0 and 1.1), such support didn't exist, and you needed to use P/Invoke to call the operating system APIs directly.

Beginning with .NET 2.0, access control lists are represented using objects, most of which are defined in the System.Security.AccessControl namespace. In the case of files, the class named FileSecurity represents file ACLs. Getting a FileSecurity object instance is easy. You can call the static GetAccessControl method of the File class in the System.IO namespace, like this:

using System.IO;
using System.Security.AccessControl;
...
string filename = @"C:\MyFolder\My File.txt";
FileSecurity security = File.GetAccessControl(filename);

With the FileSecurity object, you are able to read and modify access control entries associated with the given file. For example, the GetAccessRules method returns a collection of access control entries (or actually, objects that represent them) that match the given parameters. For example, Windows allows access control lists to inherit from parent directories, and when you call the GetAccessRules method, you need to select whether you want to include such entries as well, for instance.

The FileSecurity object also supports multitude of other features, such as controlling audit policies (technically system ACLs), and adding, editing, and deleting (discretionary) access control entries, which are the focus of this article.

To enumerate access control entries inside a FileSecurity object, you would call its GetAccessRules method. This method returns an AuthorizationRuleCollection object, which, as the name implies, is a collection all the access rules (access control entries) for the given file. Calling the GetAccessRules method requires two boolean values (to indicate whether explicit and inherited ACEs should be included) and a target type.

Of these parameters, the type is the most difficult one to specify; therefore, an explanation is in order. You might be aware that Windows uses SIDs or security identifiers to uniquely identify users or groups. SIDs are often represented as alphanumeric strings. For example, the SID string "S-1-1-0" specifies "Everybody". However, it might be easier for users to manipulate user accounts or groups with their names, for instance, "Administrator" or "Backup Operators".

The third parameter of the GetAccessRules method requires the aforementioned target type. This type specifies whether you want to read the actual SID values, or the account objects (and their names). Thus, you can either give the method the SecurityIdentifier type or the NTAccount type, respectively. Both these classes live in the System.Security.Principal namespace.

Retrieving ACE Information

To retrieve ACE information from a given file, use code similar to this:

FileSecurity security = File.GetAccessControl(filename);
AuthorizationRuleCollection acl = security.GetAccessRules(
   true, true, typeof(System.Security.Principal.NTAccount));
foreach (FileSystemAccessRule ace in acl)
{
   // Do something with the ACE here...
}

When you call the GetAccessRules of the FileSecurity class, the returned AuthorizationRuleCollection contains FileSystemAccessRule objects. An example application that is able to display there entries is shown in Figure 2. The application uses the following code to construct string-based dump of the given ACE object:

private string GetAceInformation(FileSystemAccessRule ace)
{
   StringBuilder info = new StringBuilder();
   string line = string.Format("Account: {0}",
      ace.IdentityReference.Value);
   info.AppendLine(line);
   line = string.Format("Type: {0}", ace.AccessControlType);
   info.AppendLine(line);
   line = string.Format("Rights: {0}", ace.FileSystemRights);
   info.AppendLine(line);
   line = string.Format("Inherited ACE: {0}", ace.IsInherited);
   info.AppendLine(line);
   return info.ToString();
}

Figure 2: The sample application is able to retrieve file access control lists and display them.

The properties of the FileSystemAccessRule class allow you to read whether the ACE is an allowing or denying ACE, who is (or are) affected by the setting, and finally the rights that are affected. If you are interested in SID values, identical code works fine, except that you must give the SecurityIdentifier type as the third parameter to the GetAccessRules method.

The Basics of Manipulating File Access Control Lists with C#

Modifying Access Control Lists

Reading access control entries can indeed be useful, but you will also need to learn how to manipulate entries in a file's access control list. Luckily, this is easy in .NET: in addition to the GetAccessRules method of the FileSecurity object, you also have the following methods:

  • AddAccessRule
  • ModifyAccessRule
  • PurgeAccessRules
  • RemoveAccessRule
  • SetAccessRule

Of these, probably only the Purge and Set methods require further explanation. Purging means in this case removing all ACE entries that affect the given user. This is different from removing (the RemoveAccessRule method), because removing means stripping away all matching entries, ignoring which user or group the ACE affects.

Here's the code that is executed when the "Add Self to ACL" button of the example application is pressed:

WindowsIdentity self = System.Security.Principal.
   WindowsIdentity.GetCurrent();
FileSystemAccessRule rule = new FileSystemAccessRule(
   self.Name, FileSystemRights.FullControl,
   AccessControlType.Allow);
// add the rule to the file's existing ACL list
FileSecurity security = File.GetAccessControl(filename);
AuthorizationRuleCollection acl = security.GetAccessRules(
   true, true, typeof(System.Security.Principal.NTAccount));
security.AddAccessRule(rule);
// persist changes
File.SetAccessControl(filename, security);

Here, the current user's account name, including the possible domain name is read by calling the static GetCurrent method of the WindowsIdentity class. Next, the code creates a new instance of the FileSystemAccessRule class, and specifies the account name, access control type, and the allow/deny indicator. The FileSystemRights enumeration contains all the possible options that can be controlled on files, for example:

  • ExecuteFile
  • FullControl
  • ListDirectory
  • Read
  • TakeOwnership
  • Write

Finally, the code reads the existing access control entries of the selected file, and then calls the AddAccessRule method of the returned FileSecurity object to add the new ACE to the access control list. Finally, the file's ACL is updated by calling File.SetAccessControl. If you would omit this last step, the actual ACL of the file would not be updated.

Real-World Uses

Making sure your applications are secure and safe to use should be a major priority in your application design. Being able to programmatically control access control lists allows you to build safer applications, improve your IT infrastructure, or even improve your application setups by setting secure defaults right from the start.

Of course, you need to have proper rights to set file access control entries. In Windows, the creator of a file is by default the owner of the file, which means that he or she can change the access control lists of that file. However, administrators also can modify access control lists or take ownership of the file. This is true in both domain-based and individual PCs.

In your C# code, if you don't have the permission to change a file's access control list, the File.SetAccessControl method raises a UnauthorizedAccessException. Although the example application manipulates only files, it helps to know that directories also have their access control entries. In .NET, the System.IO.Directory class also has the convenient GetAccessControl and SetAccessControl methods. These methods are identical to their file cousins, but work with objects of different types.

Note that you might also wish to test whether a user has access to a certain file. Although theoretically you could parse the access control list and investigate the ACEs in code, I strongly recommend against that because access control is a complex thing and difficult to develop correctly.

Instead, I recommend letting the operating system do the checking. Although .NET doesn't support a similar feature that the Win32 API function AccessCheck does, you can try simply opening a file, and then checking whether the user was allowed to do that or not. If the opening fails, you know that the user doesn't have access. Of course, using P/Invoke is another option.

Conclusion

In this article, you have learned the basics of access control (as far as files are concerned), and how access control works in Windows-based operating systems using the NTFS file system. You also learned what access control lists (ACLs) and access control entries (ACEs) are.

Most importantly, you learned how to read and write access control lists from your C# applications. By using the static GetAccessControl and SetAccessControl methods of the System.IO.File class, you are able to retrieve the FileSecurity object, which in turn allows you to retrieve a collection of ACEs associated with the file.

Once you master file access control, you can proceed to manipulate access control entries in other operating system objects, such as registry keys and mutexes. The .NET Framework is built so that similar code can be used no matter which object type is in question. But, that's a topic for another article.

Happy security programming!

About the Author

Jani Järvinen is a software development trainer and consultant in Finland. He is a Microsoft C# MVP and has written dozens of magazine articles and published two books about software development. He is a group leader of a Finnish software development expert group at ITpro.fi. His frequently updated blog can be found at http://www.saunalahti.fi/janij/. You can send him mail by clicking on his name at the top of the article.



About the Author

Jani Jarvinen

Jani Jarvinen is a software development trainer and consultant in Finland. He is a Microsoft C# MVP, a frequent author and has published three books about software development. He is the group leader of a Finnish software development expert group at ITpro.fi and a board member of the Finnish Visual Studio Team System User Group. His blog can be found at http://www.saunalahti.fi/janij/. You can send him mail by clicking on his name at the top of the article.

Downloads

Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Today's agile organizations pose operations teams with a tremendous challenge: to deploy new releases to production immediately after development and testing is completed. To ensure that applications are deployed successfully, an automatic and transparent process is required. We refer to this process as Zero Touch Deployment™. This white paper reviews two approaches to Zero Touch Deployment--a script-based solution and a release automation platform. The article discusses how each can solve the key …

  • Learn How A Global Entertainment Company Saw a 448% ROI Every business today uses software to manage systems, deliver products, and empower employees to do their jobs. But software inevitably breaks, and when it does, businesses lose money -- in the form of dissatisfied customers, missed SLAs or lost productivity. PagerDuty, an operations performance platform, solves this problem by helping operations engineers and developers more effectively manage and resolve incidents across a company's global operations. …

Most Popular Programming Stories

More for Developers

RSS Feeds