This chapter discusses the following topics:
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:
An access ACL is associated with a fileor directory, and is used for access control.
A default ACL is associated with a directory. When a directory has a default ACL, files and directories created in the directory are protected with DAC attributes that are derived from the default ACL and the mode specified by the object creation system call.
The POSIX ACL policy has been defined by the POSIX P1003.6 DAC security specification and features the following:
An unordered ACL with a dependent relationship on the UNIX discretionary access control mechanism
A mask that limits the access to additional ACL entries
Inheritance rules that allow the presence of a default ACL on directories
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.
The following terms describe discretionary access to objects:
Either the subject is granted read permission by the
object's mode and ACL, or the subject has the
allowdacaccess
privilege.
Either the subject is granted write permission by
the object's mode and ACL, or the subject has the
allowdacaccess
privilege.
Either the subject is granted execute permission
by the object's mode and ACL, or the subject has the
allowdacaccess
privilege.
Either the subject is granted search permission
by the directory's mode and ACL, or the subject has the
allowdacaccess
privilege.
Either the subject's EUID is the same as the object's
UID, or the subject has the
owner
privilege.
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. |
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;
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.
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 */
}
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.
| 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 |
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).
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.
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.
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.
Your program must consider the consequences of modifying the mask entry because the change might affect the effective access granted to existing ACL entries.
The ACL must be valid according to the following POSIX ACL rules:
It must have at least the three base entries
If it has more than the three base entries, it must additionally contain a mask entry
The user entries must have unique valid qualifiers
The group entries must have unique valid qualifiers
The user and group identifiers must be valid
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.
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:
This demonstrates the use of the allocation call for a working storage representation of the ACL. [Return to example]
A new ACL entry is allocated with this call. The tag type, qualifier, and permissions have an unspecified type. [Return to example]
The
pw_nametoid()
routine is an optimized
mapping from user name to ID.
It is described in
pw_mapping(3).
[Return to example]
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]
The
gr_nametoid()
routine is an optimized
mapping from group name to ID.
It is described in
pw_mapping(3).
[Return to example]
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:
Allocate memory before retrieving an ACL. [Return to example]
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]
To reset the ACL to rescan its entries, call
acl_rewind().
[Return to example]
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]
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:
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]
The mask effectively reduces the permissions of all user and group entries to (at most) read-only. [Return to example]
The permissions of the group entry is reduced to no permissions because the mask does not include execute permission. [Return to example]
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.