Inside Solaris by Jim Mauro

Controlling permissions with ACLs

We continue our files theme with a discussion of a recent addition to Solaris file security -- access control lists

SunWorld
June  1998
[Next story]
[Table of Contents]
[Search]
Subscribe to SunWorld, it's free!

Abstract
File security in Solaris has up until recently been limited to the file mode bits that provide the traditional coarse-grained levels of protection. This month, we'll look at how access control lists are used and implemented in the Solaris kernel. (2,800 words)


Mail this
article to
a friend
As Solaris gains acceptance in the mainstream commercial computing space, we see an increasing need for finer grained file access security methods. The means by which access was granted or denied for a file or directory in the file system required setting the file mode bits via chmod(1). The file modes allow for granting or denying read, write, and execute permissions for three different classes of users -- the file's owner (typically referred to as "user"), users that belong to the same group as the owner (group), and everyone else on the system (other). Such a scheme does not provide much flexibility because there is no way to specify a unique set of permissions for a particular user or group.

As per our discussions on files in previous columns, a file's mode bits are maintained in the inode (the i_mode field) for the file, had a default value for newly created files based on the user's umask, and could be altered by the file's owner using chmod(1) with either symbolic or absolute (octal) notation (file ownership is changed via chown(1)). Where appropriate, file security in Solaris today can still be maintained this way. However, for those systems that require more flexibility and tighter control, we now have access control lists, or ACLs, which were first introduced in Solaris 2.5.1.

ACLs give a file owner the ability to provide access permissions of files and directories in a UFS (Unix File System, the default file system in Solaris) for specific users and groups. In addition, a file's owner can define a default set of permissions on a directory such that all files created in that directory will have the same set of ACLs. Support for ACLs exists in Solaris today on the following file systems types: UFS (Unix File System), NFS (Network File System, Versions 2 and 3), CacheFS (Cache File System), and LOFS (Loopback File System). Other file system types in Solaris have no knowledge of ACLs, and thereby no means of applying the protections that may exist within a file's ACLs. Also, ACLs can only be applied to directories, regular files, FIFOs, and symbolic links.

We'll start by looking briefly at the end user interface to ACLs, then examine the actual implementation by operating system.

The format for a file ACL is two or three columns separated by a colon:

entry_type:[uid|gid]:perms

The first column, entry_type, defines the ACL entry as set for the user, group, other or an ACL mask. The second column is (optionally) the user ID (UID) or username for user entry types, or group ID (GID) or groupname for group entry types. For entry types other and mask, this column does not apply and thus is not required. The third column is for file permissions. File permissions take either the familiar rwx (read/write/execute) symbolic form, or the numeric form in octal notation (e.g., 7 for rwx, 4 for r--, 6 for rw-, etc.), both of which are exactly identical to the format used for chmod(1). The internal representation as a data structure looks like this (from /usr/include/sys/acl.h):

typedef struct acl {
	int a_type;	/* entry type */
	uid_t a_id;	/* UID | GID  */
   o_mode_t a_perm;	/* permissions */
} aclent_t;

Each field in the above data structure corresponds to the ACL entry described above.

When the optional username (or UID) for a user entry type is excluded, and the groupname (or GID) for a group entry is empty, the traditional Solaris file permissions are being applied (we'll illustrate this in the example below).

Users set, modify, and delete ACLs using setfacl(1) and examine file ACLs using getfacl(1). The quick example below illustrates the use of file ACLs. The left hand column is numbered to provide a reference for the description that will follow.

1  sunsys> ls -l file1
2  -rwxr-xr--   1 jim      staff        130 May 25 22:13 file1
3  sunsys> chmod 000 file1
4  sunsys> ls -l file1
5  ----------   1 jim      staff        130 May 25 22:13 file1
6  sunsys> setfacl -s user::rw-,group::r--,other:r-- file1
7  sunsys> ls -l file1
8  -rw-r--r--   1 jim      staff        130 May 25 22:13 file1
9  sunsys> getfacl file1
10 # file: file1
11 # owner: jim
12 # group: staff
13 user::rw-
14 group::r--              #effective:r--
15 mask:r--
16 other:r--
17 sunsys> setfacl -m user:moe:rw- file1
18 sunsys> ls -l file1
19 -rw-r--r--+  1 jim      staff        130 May 25 22:13 file1
20 sunsys> getfacl file1
21 # file: file1
22 # owner: jim
23 # group: staff
24 user::rw-
25 user:moe:rw-            #effective:r--
26 group::r--              #effective:r--
27 mask:r--
28 other:r--

We first did a long listing of a file called file1 and set all mode bits to zero with chmod(1) (lines 1-5). Line 6 shows the setfacl(1) command with the -s flag, which is the set acl flag. The setfacl(1) command executed on line 6 does not define special permissions for a particular user or group. It is essentially the equivalent of executing a chmod 644 file command on the same file.

The long listing on line 7 shows that we now have established file permissions that are reflected in the file mode bits. Using getfacl(1) (line 9), we display the file permissions in ACL format. Now, at this point, we technically do not have an ACL for this file. The Solaris kernel looks at the ACLs being set, and for simple cases where the setfacl(1) command is simply establishing the traditional file permissions for user, group, and other, an actual ACL is not allocated for the file. The kernel applies the permissions defined in the setfacl(1) command line to the file mode field in the inode.

On line 17 we establish read/write permissions for user Moe, using the m (modify) flag with setfacl(1). The subsequent long listing (line 18) and examination of the file ACL (line 20) reveal something interesting. On line 19, note the plus (+) sign at the end of the file modes. This tells us that an ACL exists for the file (the plus sign was absent from the listing on line 8). Now when we run the getfacl(1) command (line 20), we see the addition of the user: moe entry in the ACL.

There are a few worthwhile points on the behavior of ACLs and the rules of precedence. The example above shows an ACL mask of r--. The ACL mask defines the maximum permissions allowed for everyone except the owner of the file; in this example, everyone except the file owner has read-only permission. However, the ACL has defined read and write permissions for Moe. So what will happen if Moe attempts to write to the file -- which will take precedence, his special permissions, or the ACL mask? The answer is, the ACL mask. Here's another quick example.

1  sunsys> getfacl file1
2  # file: file1
3  # owner: jim
4  # group: staff
5  user::rw-
6  user:moe:rw-            #effective:r--
7  group::r--              #effective:r--
8  mask:r--
9  other:r--
10 sunsys> su moe
11 Password: 
12 $ id
13 uid=2001(moe) gid=22(stooges)
14 $ echo "write test" >> file1
15 file1: cannot create
16 $ exit
17 sunsys> setfacl -m m:rw- file1
18 sunsys> getfacl file1
19 # file: file1
20 # owner: jim
21 # group: staff
22 user::rw-
23 user:moe:rw-            #effective:rw-
24 group::r--              #effective:r--
25 mask:rw-
26 other:r--
27 sunsys> su moe
28 Password: 
29 $ echo "write test" >> file1
30 $ exit
31 sunsys> 

In this example we displayed the ACL of file1 (lines 1 to 9) that show the mask is set for read only (8), but Moe has read/write permissions (6). After becoming user Moe (10-13), we attempt to write to file1 (14), and the write fails (15, with a rather non-descriptive error message).

We reset the ACL mask using setfacl(1) (line 17 -- and verified the change (18-26; note the mask set to read/write on line 25). Now we try the test again, again becoming user Moe (line 17 -- I had to revert to user Jim in order to change the ACL mask, since only the file owner can alter the ACL). This time, the test (29) is successful. Thus, we see that the ACL mask will supercede individual user permissions. Note that in the setfacl(1) command on line 17, we abbreviated the entry type mask using just the letter m. The command allows for this type of abbreviation, such that the letters u and g can be used in place of user and group, respectively.

We obviously haven't covered every possible permutation of ACL permission settings. The goal here was to provide enough introductory information to get the reader off to a comfortable start using ACLs. For more information on the specifics of using ACLs, consult the setfacl(1) and getfacl(1) man pages, as well as the Solaris documentation set. For working with file ACLs programmatically, there are system calls, acl(2) and facl(2), and library routines, aclcheck(3) and aclsort(3).


Advertisements

A look inside
The kernel implementation of ACLs requires a means of storing the ACLs on disk, as well as an incore representation for opened files. The linkage to a file's ACLS begins with the inode. A pointer, i_shadow, is defined in the file's inode and is set as a pointer to a shadow inode when an ACL is created for a file. The shadow inode is not a special type of inode; it is not defined by a specific shadow inode data structure. Rather, it is a generic UFS inode used for a special purpose -- that purpose being to provide a means of storing file ACL information on disk.

The representation of file ACLs in memory involves several data structures that are rooted at the memory resident inode for the file, in a pointer called i_ufs_acl. The i_ufs_acl pointer references a data structure that represents a memory resident shadow inode, the si structure. There are many fields in a generic UFS inode that are not required when the inode is used as a shadow inode, so it does not make sense to cache an entire inode in memory when it is a shadow inode. The si structure defines the shadow inode fields that are of use in the implementation of ACLs.

There is further linkage of additional kernel data structures from the si structure that are used by the kernel for ACLs. Reference Figure 1 for the complete picture.


The incore shadow inode structure contains various fields that the kernel uses for its implementation of ACLs. The s_signature field is used to store a unique signature for the ACL. The signature is generated when ACLs are first created for a file, and a shadow inode must be retrieved or allocated from the cache (see below). The signature is simply a checksum of the various ACL objects (users, groups, etc.) that exist for the file and provide an easy way for the kernel to locate an ACL in the shadow inode cache when it invokes a search. Other fields in the incore shadow inode are similar to other kernel data structures we've looked at in past columns -- we have a reference count (s_ref), a flag field (s_flag), and a lock for protection against unsynchronized, concurrent access (s_lock).

The remaining structures linked to and from the incore shadow inode are the ic_acl (incore acl) and ufs_ic_acl structures, in which the actual ACL objects are stored. As per Figure 1, the incore shadow inode contains two ic_acl structures: one for the ACLs (s_a) and one for the default ACLs (s_d), if any exist. Each ic_acl structure entry is actually a pointer to a ufs_ic_acl, of which there is one for the owner, group, other, and each user and group for which a specific ACL entry has been created.

The kernel does a couple optimizations in its ACL implementation. First, shadow inodes are cached in memory. Second, shadow inodes are shared when appropriate. This means that a group of files that have the same owner and the same ACLs defined for every file will reference the same shadow inode for the storage and retrieval of the ACL objects in each file. This would be the case, for example, if a default ACL were defined by a user for a directory, and files started being added to that directory. Every file with the same owner would have the same ACL unless explicitly changed via setfacl(1)).

This system uses kernel memory more effectively, as we're not replicating the same kind of information (i.e., ACLs) in the kernel. Utilization of file system resources is also minimized this way: As long as the file has the same owner and the ACLs defined are identical the kernel doesn't have to allocate a shadow inode for every file that has an ACL (resulting in the same signature, as above).

A couple of counters are maintained by the kernel for maintaining shadow inode cache hits and misses: si_cachehit and si_cachemiss. I am not aware of any bundled shell command that makes this information available, but it can be examined using adb(1) as follows.

# adb -k /dev/ksyms /dev/mem	** note you must be root, type this to invoke adb 
physmem fdde			** adb returns this when invoked
si_cachehit/D			** type this. 
si_cachehit:			** adb returned this
si_cachehit:    2187		** adb returned this
				** type carriage return here
si_cachemiss:			** adb returned this
si_cachemiss:   13		** adb returned this

$q				** type this to exit adb

# 

You can do some quick arithmetic to calculate your ACL cache hit rate: 2187 + 13 = 2200 cache references. 2187 / 2200 * 100 = 99.41, thus our example shows a 99.41 percent cache hit rate. The shadow inode (ACL) cache is not tunable. There are no kernel variables that can be set in the /etc/system file in increase or decrease the shadow inode cache.

When an ACL is created for a file, the kernel first searches the shadow inode cache to see if a resident shadow inode exists that could be used, based on the shadow inode signature. In the case of a cache hit, the pointer linkage is done, and the reference count for the shadow inode (s_ref) is incremented. If no shadow inode exists, the kernel calls the same ufs_ialloc() (allocate a UFS inode) routine that's used for allocating inodes for newly created files. Once the inode is allocated, the i_shadow pointer in the file's original inode is set to point to it, and the i_mode field in the newly allocated shadow inode is set to IFSHAD, indicating that this inode is a shadow inode.

ACL information and access is linked to the vnode layer, such that the kernel can provide a generic set of file system interfaces to access ACL data, keeping the VFS/Vnode layer consistent with its original design intention (see earlier columns for more information on vnodes). The system defines VOP_GETSECATTR and VOP_SETSECATTR operations (get_secondary_attributes and set_secondary_attributes) for accessing file ACL information through the standard VFS/Vnode interface. A data structure for this purpose is defined in vnode.h, the vsecattr structure, which, when initialized, will point to the ACL entries and default ACL entries for the file that the vnode is allocated to. The kernel actually checks for the existence of VOP_GETSECATTR and VOP_SETSECATTR in the vfs operations structure for a file system to determine if a particular file system supports ACLs.

On disk, the ACLs are stored the same way as file data is, referenced through the direct and indirect block pointers in the inode. Thus, the blocks pointed to by the shadow inode's i_db[] array will hold the actual ACL objects. The kernel defines a maximum of number of ACL entries that can be created per file of 1024 (1k). Given that each ACL entry only occupies 12 bytes, the actual storage of ACLs on disk typically does not require very much space, although a lot of files with a large number of unique ACL entries per file could add up (and that's not even counting inode allocation). If you're looking at a file system and trying to account for used data blocks and inodes, don't forget to consider ACL use.

That's a wrap for this month. Next month we'll cover large files in Solaris.


Resources


About the author
Jim Mauro is currently an area technology manager for Sun Microsystems in the Northeast area, focusing on server systems, clusters, and high availability. He has a total of 18 years industry experience, working in service, educational services (he developed and delivered courses on Unix internals and administration), and software consulting. Reach Jim at jim.mauro@sunworld.com.

What did you think of this article?
-Very worth reading
-Worth reading
-Not worth reading
-Too long
-Just right
-Too short
-Too technical
-Just right
-Not technical enough
 
 
 
    

SunWorld
[Table of Contents]
Subscribe to SunWorld, it's free!
[Search]
Feedback
[Next story]
Sun's Site

[(c) Copyright  Web Publishing Inc., and IDG Communication company]

If you have technical problems with this magazine, contact webmaster@sunworld.com

URL: http://www.sunworld.com/swol-06-1998/swol-06-insidesolaris.html
Last modified: