21    Discretionary Access Control

This chapter discusses the following topics:

21.1    Introduction to ACLs

ACLs are the method used to implement discretionary access control. ACLs provide a more granular discretionary access control mechanism than traditional UNIX permission bits. All objects are considered to be protected by an ACL. An object protected by traditional UNIX DAC has a base entry ACL. A base entry ACL contains the three required entries (user, group, and other) that correspond to the traditional UNIX permission bits. If additional entries are added to the ACL, they are evaluated according to entry type by rules defined by the POSIX specification.

There are two types of ACLs:

The POSIX ACL policy has been defined by the POSIX P1003.6 DAC security specification and features the following:

You can apply ACLs to any traditional UNIX object that uses permission bits for access control, such as files (and directories) and System V IPC objects (semaphores, message queues, and shared memory segments). Symbolic links have permission bits that are initialized according to the usual rules but are never used for access control; ACLs on this file type are not supported. The guidelines in this chapter apply to all objects that can be protected by ACLs.

See acl(4) for a description of POSIX ACL support. See acl(3) for information on the ACL library routines.

21.2    DAC Access Definitions

The following terms describe discretionary access to objects:

read access

Either the subject is granted read permission by the object's mode and ACL, or the subject has the allowdacaccess privilege.

write access

Either the subject is granted write permission by the object's mode and ACL, or the subject has the allowdacaccess privilege.

execute access

Either the subject is granted execute permission by the object's mode and ACL, or the subject has the allowdacaccess privilege.

search access

Either the subject is granted search permission by the directory's mode and ACL, or the subject has the allowdacaccess privilege.

owner access

Either the subject's EUID is the same as the object's UID, or the subject has the owner privilege.

21.3    DAC Privileges

The following privileges affect DAC:

Privilege Policy
allowdacaccess Lets a process override DAC checks when accessing objects. Note that allowdacaccess does not override the ownership restriction, as does owner.
chmodsugid Enables the user to set the SUID/SGID bits on a file. A process can set a file's SUID bit if the file owner ID is equal to the process's effective UID, or if the process has the setprocident privilege. A process can set a file's SGID bit if the file group ID is equal to the process's effective GID or any of the process's supplementary GIDs, or if the process has the setprocident privilege.
chown A process with the chown privilege can change the owner or group of an object. A process does not have to hold the chown privilege to change the group of an object to the process effective group or any of the process's supplementary groups.
owner Lets a process act as the owner of any object. The owner privilege alone does not grant discretionary access. However, a process with both owner and chown could change the ownership of an object and then change the object's DAC permissions.

21.4    ACL Data Representations

An ACL has two internal and one external representation. The external representation consists of text and is used to enter and display ACLs. Library routines manipulate ACLs in working storage in an internal representation that is only indirectly accessible to the calling routine. This internal representation can be interpreted with the acl.h header file. The other internal representation is a data (binary) package, which consists of ACL entries only, and is in packed format, without padding. ACL entry contents can be converted to exportable text or contiguous persistent binary.

All data representations use the following basic types:

typedef unsigned short  acl_tag_t;
typedef unsigned short  acl_permset_t;
 
/*      acl_tag_t values        */
 
#define BOGUS_OBJ               0
#define USER_OBJ                1
#define MASK_OBJ                2
#define USER                    3
#define GROUP_OBJ               4
#define GROUP                   5
#define OTHER_OBJ               6
 
/*      acl_permset_t values    */
 
#define ACL_NOPERM              0x0
#define ACL_PEXECUTE            0x1
#define ACL_PWRITE              0x2
#define ACL_PREAD               0x4
 
/*      tag qualifier type      */
 
typedef union {
        uid_t   _acl_uid;
        gid_t   _acl_gid;
} acl_id_type;

21.4.1    Working Storage Representation

Many ACL routines manipulate the working storage representation, which is a set of opaque data structures for ACLs and ACL entries. Your program should operate on these data structures only through the defined routines. Because the working storage data structures are subject to change, the interface is the only reliable way to access the data.

The working storage representation is not contiguous in memory. Also, a program cannot determine the sizes of ACL entries and ACL descriptors. The working storage data structures contain internal pointer references and are therefore meaningless if passed between processes or stored in a file. A program can convert the working storage representation of an ACL to other representations of an ACL.

21.4.2    Data Package Representation

The data package represents an ACL in a contiguous section of memory that is independent of the process address space in which it resides. Because it is contiguous and does not have internal pointer references, the data package can be passed between processes or stored in a file.

The acl_data_t data structure associated with the data package is visible at the library interface. The structure contains an aclh_t header followed by an array of acle_t entries. The following structure declarations are from the #if SEC_ACL_POSIX portion of acl.h:

/*
 *      Header for data package type.
 */
 
typedef struct {
        ushort  acl_num;
        ushort  acl_magic;
} aclh_t;
 
/*
 *      Descriptor for a specific ACL entry
 *      in internal representation.
 */
 
typedef struct {
        acl_tag_t       acl_tag;
        acl_permset_t   acl_perm;
        acl_id_type     acl_id;
} acle_t;
 
/*
 *      Descriptor for specific ACL entry
 *      in (data package format).
 */
 
struct acl_data {
       aclh_t       acl_hdr;
/*     acle_t       *acl_entry;  comment; acl entries follow */
};
 
typedef struct acl_data         *acl_data_t;

The following code fragment operates on all entries in a data package ACL representation:

acl_data_t     data_p;
acle_t         *entry_p;
int            i;
 
entry_p = (acle_t *) (data_p + 1);
          /* The pointer arithmetic addresses the first
             acl entry.*/
 
for (i = 0; i < data_p->acl_num; i++, entry_p++) {
 
          /* reference the ACL entries through entry_p */
}

21.4.3    External Representation

The human-readable external representation of an ACL consists of a sequence of lines, each of which is terminated by a new-line character. The POSIX routines use the external representation when converting between the working storage representation and the text package.

The external representation is described in acl(3). Table 21-1 shows the structure of individual entries.

Table 21-1:  ACL Entry External Representation

Entry Type acl_tag_t Value Entry
base user USER_OBJ user::perms
mask MASK_OBJ mask::perms
base group GROUP_OBJ group::perms
base other OTHER_OBJ other::perms
user USER user:user_name:perms
group GROUP group:group_name:perms

21.5    Default ACL

When a directory has a default ACL, files and directories created in the directory are protected with DAC attributes derived from the default ACL and the mode specified by the object creation system call.

To set a default ACL on a directory, call acl_write() with ACL_TYPE_DEFAULT as the second argument. To retrieve a default ACL, call acl_read() with ACL_TYPE_DEFAULT as the second argument.

Because a default ACL does not include an owner ID or group ID, a file-system object does not inherit its owner and group from the default ACL associated with its parent directory. The file owner is taken from the process creating the object, and the group is inherited from the parent directory.

The inheritance rules are described in detail in acl(4).

21.6    ACL Rules

Some interactions between the ACL and the UNIX permissions are subtle. Unless you understand the interaction between ACL routines and the system calls that manipulate UNIX DAC attributes, you might get different permissions than you intended.

The following sections describe rules for programs that handle ACLs.

21.6.1    Object Creation

When copying one file to another, it is a common practice for a program to create a new file and propagate the owner, group, and mode. If the source file has an ACL, it is a good practice to propagate that ACL to the target file in all cases where the UNIX DAC attributes are propagated.

When a parent directory has a default ACL, the owner of the created file is inherited from the process and its group is inherited from the parent directory. In addition, the specified mode is used instead of the process umask. Therefore, your program must set the mode when creating the object, and must not depend on the umask to protect objects.

21.6.2    ACL Replication

Programs that replicate permissions must preserve the ACL. The discretionary protection of the object is no longer described by the owner, group, and permissions. A program should copy the ACL in order to maintain proper discretionary protection.

21.6.3    Mask Entries

Your program must consider the consequences of modifying the mask entry because the change might affect the effective access granted to existing ACL entries.

21.6.4    ACL Validity

The ACL must be valid according to the following POSIX ACL rules:

Note that the routines described in the reference pages do not recompute the mask as does the chacl command. If the user of your application expects similar behavior to the chacl command, you must emulate that behavior in your program.

21.7    Example - ACL Creation

Assume that you want to set a file's access ACL to the following permissions:

user::rwx
mask::r-x
user:june:r-x
user:sally:r-x
 
group::rwx      #effective:r-x
group:mktg:rwx  #effective:r-x
 
other::r-x

The following code takes the tabular form of the ACL, creates a working storage representation of the ACL, and applies it to a file:

struct entries {
    acl_tag_t     tag_type;
    char         *qualifier;
    acl_permset_t perms;
} table[] = {
  { USER_OBJ,  NULL,    ACL_PRDWREX },
  { MASK_OBJ,  NULL,    ACL_PRDEX }
  { USER,      "june",  ACL_PRDEX },
  { USER,      "sally", ACL_PRDEX },
  { GROUP_OBJ, NULL,    ACL_PRDWREX },
  { GROUP,     "mktg",  ACL_PRDWREX },
  { OTHER_OBJ, NULL,    ACL_PRDEX }
};
 
#define TABLE_ENTRIES (sizeof(table)/sizeof(table[0]))
 
acl_t       acl_p;
acl_entry_t entry_p;
int         i;
uid_t       uid;
gid_t       gid;
 
/*  allocate an ACL */
acl_alloc(&acl_p);                          [1]
 
/* walk through the table and create entries */
for (i = 0; i < TABLE_ENTRIES; i++) {
 
     /* allocate the entry */
     acl_create_entry(acl_p, &entry_p);     [2]
 
     /* set the permissions */
     acl_set_perm(entry_p, table[i].perms);
 
     /* setting the tag type and qualifier depends
        on the type */
     switch (table[i].tag_type) {
 
     case USER:
 
        /* map user name to ID and specify as qualifier */
 
        uid = pw_nametoid(table[i].qualifier);  [3]
        acl_set_tag(entry_p, table[i].tag_type,
                    (void *) uid);              [4]
 
        break;
 
     case GROUP:
 
        /* map group name to ID and specify as qualifier */
 
           gid = gr_nametoid(table[i].qualifier);  [5]
           acl_set_tag(entry_p, table[i].tag_type,
                       (void *) gid);
           break;
 
 
     default:
 
           /* qualifier is NULL for other types */
 
           acl_set_tag(entry_p, table[i].tag_type, NULL);
           break;
     }
}
 
 
/* set the ACL on the file */
 
if (acl_write(filename, ACL_TYPE_ACCESS, acl_p) < 0)
    perror(filename);
 
/* free storage allocated for the ACL */
 
acl_free(acl_p);

Notes:

  1. This demonstrates the use of the allocation call for a working storage representation of the ACL. [Return to example]

  2. A new ACL entry is allocated with this call. The tag type, qualifier, and permissions have an unspecified type. [Return to example]

  3. The pw_nametoid() routine is an optimized mapping from user name to ID. It is described in pw_mapping(3). [Return to example]

  4. The acl_set_tag() function takes a void argument for the qualifier, and casts it to the appropriate data type depending on obj_type. [Return to example]

  5. The gr_nametoid() routine is an optimized mapping from group name to ID. It is described in pw_mapping(3). [Return to example]

21.8    Example - Mask Recomputation

The following program recomputes the mask value for a file's ACL by scanning the permission fields of existing ACL entries and setting the mask entry's permissions to the union of all permissions in the entries affected by the mask. If the ACL contains only the base entries, no mask entry is added.

A compilable version of this program is in /usr/examples/MLS/programming_guide/change_acl.c.

/*
 * change_acl.c
 *
 * This program changes the acl on the specified file.
 *
 * compile:	cc change_acl.c -o change_acl -lsecurity
 *
 * setup:    Add a group entry to a file's ACL, but do not
 *           update the mask. For example:
 *
 *                 chacl -x -u group:<name>:<perms>  <file>
 *
 *            Use lsacl to verify that the mask is not updated:
 *
 *                  lsacl <file>
 *
 * run:       change_acl <file>
 *
 * result:    Use lsacl to verify that the mask is updated.
 */
 
#include <cmw.h>
#include <acl.h>
#include <stdio.h>
 
main(argc, argv)
int      argc;
char    *argv[];
{
        acl_entry_t       acle_p;
        acl_entry_t       mask_entry_p;
        acl_permset_t     perms;
        acl_t             acl_p;
        int               entries;
        acl_tag_t         tag_type;
        acl_id_type       tag_qualifier;
 
        /* allocate the ACL */
        if (acl_alloc(&acl_p) ) {
            fprintf(stderr, "acl_alloc error ");  [1]
            exit(1);
        }
 
        /* retrieve the ACL from the specified file */
        entries = acl_read(argv[1], ACL_TYPE_ACCESS, acl_p);
 
        if (entries < 0) {
            fprintf(stderr, "acl_read on ");
            psecerror(argv[1]);
            exit(1);
        }
 
        /* if it is a base ACL, there's nothing to do
                                      (no mask entry) */
        if (entries == 3)
            exit(0);
 
        /* scan the ACL for the mask entry */
        while (acl_get_entry(acl_p, &acle_p) == 1) {
               acl_get_tag(acle_p, &tag_type, &tag_qualifier); [2]
               if (tag_type == MASK_OBJ) {
                   mask_entry_p = acle_p;
                   break;
                }
        }
 
        /* set to re-scan the ACL */
        acl_rewind(acl_p);                          [3]
 
        /* add permissions in all ACL entries to the mask entry */
        while (acl_get_entry(acl_p, &acle_p) == 1) {
               acl_get_tag(acle_p, &tag_type, &tag_qualifier);
               switch (tag_type) {
 
               case MASK_OBJ:
               case USER_OBJ:
               case OTHER_OBJ:
                       break;
 
               /* add permissions of all entries to the mask's
                                                  permissions */
 
               case GROUP_OBJ:
               case USER:
               case GROUP:
                    acl_get_perm(acle_p, &perms);
                    acl_add_perm(mask_entry_p, perms); [4]
                    break;
               }
        }
 
        /* Change the ACL on the object */
        if (acl_write(argv[1], ACL_TYPE_ACCESS, acl_p) < 0) {
            fprintf(stderr, "acl_write on ");
            psecerror(argv[1]);
            exit(1);
        }
        exit(0);
}

Notes:

  1. Allocate memory before retrieving an ACL. [Return to example]

  2. You must retrieve both the tag_type and tag_qualifier from the ACL entry. (If you want to retrieve only one parameter, you cannot specify a NULL pointer for the other parameter.) [Return to example]

  3. To reset the ACL to rescan its entries, call acl_rewind(). [Return to example]

  4. Because the permission data type is opaque, use the supplied routines to add permissions. If you do not want to set permissions on an existing ACL entry, allocate an ACL and an ACL entry to provide a place to manipulate permissions. [Return to example]

21.9    Example - ACL Inheritance

This section shows how a program can specify a default ACL on a directory and then describes what happens when a file and a directory are created in that directory.

Assume that directory /usr/john/acl_dir has the following access and default ACL:

# lsacl /usr/john/acl_dir
 
# file:/usr/john/acl_dir
# owner:john
# group:prog
user::rwx
mask::r-x
user:june:r-x
user:sally:r-x
 
group::rwx      #effective:r-x
group:mktg:rwx  #effective:r-x
 
other::r-x
 
# lsacl -d /usr/john/acl_dir
 
# file:/usr/john/acl_dir
# owner:john
# group:prog
user::rwx
mask::r-x
user:june:r-x
user:sally:r-x
user:bin:rwx    #effective:r-x
user:tcb:rwx    #effective:r-x
 
group::rwx      #effective:r-x
group:mktg:rwx  #effective:r-x
 
other::r-x

The following code updates the default ACL to remove read and write permission from the mktg group entry:

acl_permset_t perms;
acl_id_type   qualifier;
acl_tag_t     tag_type;
acl_t         acl;
acl_entry_t   acl_entry;
char         *filename;
gid_t         mktg_gid;
 
/* map the "mktg" group name to an ID */
mktg_gid = gr_nametoid("mktg");
 
/* allocate an ACL entry */
acl_alloc(&acl);
 
/* read the default ACL from the file */
acl_read(filename, ACL_TYPE_DEFAULT, acl);
 
/* scan the ACL looking for the entry */
while (acl_get_entry(acl, &acl_entry) == 1) {
 
     /* retrieve the tag type and qualifier */
     acl_get_tag(acl_entry, &tag_type, &qualifier);
 
    /* check for appropriate entry and remove 'r' and 'w' */
    if (tag_type == GROUP && qualifier.acl_gid == mktg_gid) {
        acl_delete_perm(acl_entry, ACL_PRDWR);
 
        /* put the new default ACL on the file */
        if (acl_write(filename, ACL_TYPE_DEFAULT, acl) < 0) {
            fprintf(stderr, "acl_write on ");
            psecerror(filename);
        }
    }
}

The following code creates a file and a directory in the directory and demonstrates the ACL inheritance rules:

#define REGULAR_FILE        "regular"
#define DIRECTORY_FILE      "dir"
 
  char         pathname[100];
  int          fd;
 
  /* Create the regular file */
  sprintf(pathname, "%s/%s", filename, REGULAR_FILE);
 
  fd = creat(pathname, 0644);
 
  /* Create the directory */
  sprintf(pathname, "%s/%s", filename, DIRECTORY_FILE);
 
  mkdir(pathname, 0700);

When the preceding code is executed, the access ACL on the newly created file and the access and default ACLs on the newly created directory are as follows:

# lsacl /usr/john/acl_dir/regular
 
# file:/usr/john/acl_dir/regular
# owner:john
# group:prog
user::rw-
mask::r--                               [1]
user:june:r-x   #effective:r--          [2]
user:sally:r-x  #effective:r--
user:bin:rwx    #effective:r--
user:tcb:rwx    #effective:r--
 
group::rwx      #effective:r--
group:mktg:--x  #effective:---          [3]
 
other::r--                              [4]

Notes:

  1. The mask is reduced to r because the specified mode for the file (0644) does not include execute permission for the group. [Return to example]

  2. The mask effectively reduces the permissions of all user and group entries to (at most) read-only. [Return to example]

  3. The permissions of the group entry is reduced to no permissions because the mask does not include execute permission. [Return to example]

  4. The other entry is set to read-only and is not affected by the mask. [Return to example]

# lsacl /usr/john/acl_dir/dir
 
# file:/usr/john/acl_dir/dir
# owner:john
# group:prog
user::rwx
mask::r-x
user:june:r-x
user:sally:r-x
 
group::rwx      #effective:r-x
group:mktg:rwx  #effective:r-x
 
other::r-x

Note that the inheritance rules for a directory created in a directory that has a default ACL are different than those for a file. Also note that the specified creation mode (0700) does not have as much of an impact as it does on a regular file creation.

# lsacl -d /usr/john/acl_dir/dir
 
# file:/usr/john/acl_dir/dir
# owner:john
# group:prog
user::rwx
mask::r-x
user:june:r-x
user:sally:r-x
user:bin:rwx    #effective:r-x
user:tcb:rwx    #effective:r-x
 
group::rwx      #effective:r-x
group:mktg:--x
 
other::r-x

Note that he default ACL is simply inherited from the directory's parent.