ACL evaluation: interaction between "best match" and privilege
Robert Watson
rwatson at FreeBSD.org
Fri Sep 22 16:27:09 GMT 2000
On Fri, 22 Sep 2000, Casey Schaufler wrote:
> If a system has audit (What? I though we we talking about ACLs.
> Yes, we are. I said it was a systemic issue, didn't I? now be
> patient) we want to record not only whether an operation
> succeeded of failed, but why. Thus, I want to do a complete
> access control check (in this case, completely resolve the ACL)
> before I check for any capabilities. Why? so that I can mark
> which capabilities are actually used in the audit record.
> If I do capability checks first, or if I intermix them with
> strict policy checking, that information can be incorrect.
I agree with this analysis, but it prompted my concern, as "best match"
results in ambiguity about which privilege to use.
Suppose the request is for VREAD|VWRITE. There are two matching group
entries, one providing ACL_PERM_READ and one providing ACL_PERM_WRITE.
The process also has CAP_DAC_READ_SEARCH and CAP_DAC_WRITE. Which
capability do I audit as used? :-) There is no "best match", as both are
best. Right now, I do first match when falling back on privilege, but
from the perspective of minimizing privilege used, it's not right unless
you define your privilege minimalization function as first match :-).
I've attached my current vaccess_acl() call, which performs an access
check given an ACL. Due to the implementation I selected, it gets
ACL_{USER,GROUP}_OBJ and ACL_OTHER information from the inode's
owner/group/mode passed as arguments, and assumes that the only entries in
the ACL are ACL_USER, ACL_GROUP, and ACL_MASK.
Note that as you suggest, checks are generally performed twice: once
without privilege available, and if there is a matched ACL entry, a second
time with privilege. In the group case, I look for a best match, and if I
don't find it, I fall back and successively try entries until I find one
that matches when all available capabilities are applied. Note also that
right now, I simply return a flag to the caller indicating that privilege
was used. Once we resolve which privilege should be recorded as used,
I'll be passing back a mask of the capabilities used, instead.
Robert N M Watson
robert at fledge.watson.org http://www.watson.org/~robert/
PGP key fingerprint: AF B5 5F FF A6 4A 79 37 ED 5F 55 E9 58 04 6A B1
TIS Labs at Network Associates, Safeport Network Services
-------------- next part --------------
/*
* Implement a version of vaccess() that understands POSIX.1e ACL semantics.
* Assumes an abbreviated ACL, in which the ACL_USER_OBJ and ACL_GROUP_OBJ
* entries are passed via the vaccess()-like file_{mode,uid,gid} arguments,
* and these entries do not appear in the ACL (or are ignored if they do).
* Should be merged into vaccess() eventually.
*/
int
vaccess_acl(enum vtype type, mode_t file_mode, uid_t file_uid,
gid_t file_gid, struct acl *acl, mode_t acc_mode, struct ucred *cred,
int *privused)
{
struct acl_entry *acl_mask;
mode_t dac_granted;
mode_t cap_granted;
mode_t acl_mask_granted;
int group_matched, i;
/*
* Look for a normal, non-privileged way to access the file/directory
* as requested. If it exists, go with that. Otherwise, attempt
* to use privileges granted via cap_granted. In some cases,
* which privileges to use may be ambiguous due to "best match",
* in which case fall back on first match for the time being.
*/
if (privused != NULL)
*privused = 0;
/*
* Determine privileges now, but don't apply until we've found
* a DAC match that has failed to allow access.
*/
#ifndef CAPABILITIES
if (suser_xxx(cred, NULL, PRISON_ROOT) == 0)
cap_granted = (VEXEC | VREAD | VWRITE);
else
cap_granted = 0;
#else
cap_granted = 0;
if (type == VDIR) {
if ((acc_mode & VEXEC) && !cap_check(cred, NULL,
CAP_DAC_READ_SEARCH, PRISON_ROOT))
cap_granted |= VEXEC;
} else {
if ((acc_mode & VEXEC) && !cap_check(cred, NULL,
CAP_DAC_EXECUTE, PRISON_ROOT))
cap_granted |= VEXEC;
}
if ((acc_mode & VREAD) && !cap_check(cred, NULL, CAP_DAC_READ_SEARCH,
PRISON_ROOT))
cap_granted |= VREAD;
if ((acc_mode & VWRITE) && !cap_check(cred, NULL, CAP_DAC_WRITE,
PRISON_ROOT))
cap_granted |= VWRITE;
#endif /* CAPABILITIES */
dac_granted = 0;
/* Check the owner. */
if (cred->cr_uid == file_uid) {
/*
* Emulate ACL_USER_OBJ entry -- should this check there
* is none in the ACL itself?
*/
if (file_mode & S_IXUSR)
dac_granted |= VEXEC;
if (file_mode & S_IRUSR)
dac_granted |= VREAD;
if (file_mode & S_IWUSR)
dac_granted |= VWRITE;
if ((acc_mode & dac_granted) == acc_mode)
return (0);
if ((acc_mode & (dac_granted | cap_granted)) == acc_mode) {
if (privused != NULL)
*privused = 1;
return (0);
}
}
/*
* Checks against remainder of the types include checks against
* any ACL_MASK that may be set. As such, locate and store the
* ACL_MASK for safe keeping. If there is none, assume an
* ACL_MASK that permits all activities.
*/
acl_mask = NULL;
for (i = 0; i < acl->acl_cnt; i++)
if (acl->acl_entry[i].ae_tag == ACL_MASK) {
acl_mask = &acl->acl_entry[i];
break;
}
if (acl_mask != NULL) {
acl_mask_granted = 0;
if (acl_mask->ae_perm & ACL_PERM_EXEC)
acl_mask_granted |= VEXEC;
if (acl_mask->ae_perm & ACL_PERM_READ)
acl_mask_granted |= VREAD;
if (acl_mask->ae_perm & ACL_PERM_WRITE)
acl_mask_granted |= VWRITE;
} else
acl_mask_granted = VEXEC | VREAD | VWRITE;
/*
* We have to check each type even if we know ACL_MASK will reject,
* as we need to know what match there might have been, and
* therefore what further types we might be allowed to check.
* Do the checks twice -- once without privilege, and a second time
* with, if there was a match.
*/
/*
* Check ACL_USER ACL entries.
*/
for (i = 0; i < acl->acl_cnt; i++) {
dac_granted = 0;
if (acl->acl_entry[i].ae_tag == ACL_USER &&
cred->cr_uid == acl->acl_entry[i].ae_id) {
if (acl->acl_entry[i].ae_perm & ACL_PERM_EXEC)
dac_granted |= VEXEC;
if (acl->acl_entry[i].ae_perm & ACL_PERM_READ)
dac_granted |= VREAD;
if (acl->acl_entry[i].ae_perm & ACL_PERM_WRITE)
dac_granted |= VWRITE;
dac_granted &= acl_mask_granted;
if ((acc_mode & dac_granted) == acc_mode)
return (0);
if ((acc_mode & (dac_granted | cap_granted)) ==
acc_mode) {
if (privused != NULL)
*privused = 1;
return (0);
}
return (EACCES);
}
}
/*
* Group match is best-match, not first-match, so find a
* "best" match. Start with ACL_GROUP_OBJ, then move on
* if it doesn't match. Make sure to keep track of whether
* or not any matches were found, because if so we must
* skip the ACL_OTHER check.
*/
group_matched = 0;
if (groupmember(file_gid, cred)) {
dac_granted = 0;
if (file_mode & S_IXGRP)
dac_granted |= VEXEC;
if (file_mode & S_IRGRP)
dac_granted |= VREAD;
if (file_mode & S_IWGRP)
dac_granted |= VWRITE;
dac_granted &= acl_mask_granted;
if ((acc_mode & dac_granted) == acc_mode)
return (0);
group_matched = 1;
}
for (i = 0; i < acl->acl_cnt; i++) {
dac_granted = 0;
if (acl->acl_entry[i].ae_tag == ACL_GROUP &&
groupmember(acl->acl_entry[i].ae_id, cred)) {
if (acl->acl_entry[i].ae_perm & ACL_PERM_EXEC)
dac_granted |= VEXEC;
if (acl->acl_entry[i].ae_perm & ACL_PERM_READ)
dac_granted |= VREAD;
if (acl->acl_entry[i].ae_perm & ACL_PERM_WRITE)
dac_granted |= VWRITE;
dac_granted &= acl_mask_granted;
if ((acc_mode & dac_granted) == acc_mode)
return (0);
group_matched = 1;
}
}
if (group_matched == 1) {
/*
* There was a match, but it did not grant rights via
* pure DAC. Try again, this time with privilege.
*/
if (groupmember(file_gid, cred)) {
dac_granted = 0;
if (file_mode & S_IXGRP)
dac_granted |= VEXEC;
if (file_mode & S_IRGRP)
dac_granted |= VREAD;
if (file_mode & S_IWGRP)
dac_granted |= VWRITE;
dac_granted &= acl_mask_granted;
if ((acc_mode & (dac_granted | cap_granted)) ==
acc_mode) {
if (privused != NULL)
*privused = 1;
return (0);
}
}
for (i = 0; i < acl->acl_cnt; i++) {
dac_granted = 0;
if (acl->acl_entry[i].ae_tag == ACL_GROUP &&
groupmember(acl->acl_entry[i].ae_id, cred)) {
if (acl->acl_entry[i].ae_perm & ACL_PERM_EXEC)
dac_granted |= VEXEC;
if (acl->acl_entry[i].ae_perm & ACL_PERM_READ)
dac_granted |= VREAD;
if (acl->acl_entry[i].ae_perm & ACL_PERM_WRITE)
dac_granted |= VWRITE;
dac_granted &= acl_mask_granted;
if ((acc_mode & (dac_granted | cap_granted)) ==
acc_mode) {
if (privused != NULL)
*privused = 1;
return (0);
}
}
}
/*
* Even with privilege, group membership was not sufficient.
* Return failure.
*/
return (EACCES);
}
/*
* Fall back on ACL_OTHER, emulated from file mode.
* ACL_MASK is not applied to ACL_OTHER.
*/
dac_granted = 0;
if (file_mode & S_IXOTH)
dac_granted |= VEXEC;
if (file_mode & S_IROTH)
dac_granted |= VREAD;
if (file_mode & S_IWOTH)
dac_granted |= VWRITE;
if ((acc_mode & dac_granted) == acc_mode)
return (0);
if ((acc_mode & (dac_granted | cap_granted)) == acc_mode) {
if (privused != NULL)
*privused = 1;
return (0);
}
return (EACCES);
}
More information about the posix1e
mailing list