SELinux: delay initialization of audit data in selinux_inode_permission

We pay a rather large overhead initializing the common_audit_data.
Since we only need this information if we actually emit an audit
message there is little need to set it up in the hot path.  This patch
splits the functionality of avc_has_perm() into avc_has_perm_noaudit(),
avc_audit_required() and slow_avc_audit().  But we take care of setting
up to audit between required() and the actual audit call.  Thus saving
measurable time in a hot path.

Signed-off-by: Stephen Smalley <sds@tycho.nsa.gov>
Signed-off-by: Eric Paris <eparis@redhat.com>
This commit is contained in:
Eric Paris 2012-04-04 15:01:42 -04:00
parent 154c50ca4e
commit 2e33405785
3 changed files with 105 additions and 70 deletions

View File

@ -458,7 +458,7 @@ static void avc_audit_post_callback(struct audit_buffer *ab, void *a)
}
/* This is the slow part of avc audit with big stack footprint */
static noinline int slow_avc_audit(u32 ssid, u32 tsid, u16 tclass,
noinline int slow_avc_audit(u32 ssid, u32 tsid, u16 tclass,
u32 requested, u32 audited, u32 denied,
struct common_audit_data *a,
unsigned flags)
@ -496,67 +496,6 @@ static noinline int slow_avc_audit(u32 ssid, u32 tsid, u16 tclass,
return 0;
}
/**
* avc_audit - Audit the granting or denial of permissions.
* @ssid: source security identifier
* @tsid: target security identifier
* @tclass: target security class
* @requested: requested permissions
* @avd: access vector decisions
* @result: result from avc_has_perm_noaudit
* @a: auxiliary audit data
* @flags: VFS walk flags
*
* Audit the granting or denial of permissions in accordance
* with the policy. This function is typically called by
* avc_has_perm() after a permission check, but can also be
* called directly by callers who use avc_has_perm_noaudit()
* in order to separate the permission check from the auditing.
* For example, this separation is useful when the permission check must
* be performed under a lock, to allow the lock to be released
* before calling the auditing code.
*/
inline int avc_audit(u32 ssid, u32 tsid,
u16 tclass, u32 requested,
struct av_decision *avd, int result, struct common_audit_data *a,
unsigned flags)
{
u32 denied, audited;
denied = requested & ~avd->allowed;
if (unlikely(denied)) {
audited = denied & avd->auditdeny;
/*
* a->selinux_audit_data->auditdeny is TRICKY! Setting a bit in
* this field means that ANY denials should NOT be audited if
* the policy contains an explicit dontaudit rule for that
* permission. Take notice that this is unrelated to the
* actual permissions that were denied. As an example lets
* assume:
*
* denied == READ
* avd.auditdeny & ACCESS == 0 (not set means explicit rule)
* selinux_audit_data->auditdeny & ACCESS == 1
*
* We will NOT audit the denial even though the denied
* permission was READ and the auditdeny checks were for
* ACCESS
*/
if (a &&
a->selinux_audit_data->auditdeny &&
!(a->selinux_audit_data->auditdeny & avd->auditdeny))
audited = 0;
} else if (result)
audited = denied = requested;
else
audited = requested & avd->auditallow;
if (likely(!audited))
return 0;
return slow_avc_audit(ssid, tsid, tclass,
requested, audited, denied,
a, flags);
}
/**
* avc_add_callback - Register a callback for security events.
* @callback: callback function

View File

@ -2684,6 +2684,11 @@ static int selinux_inode_permission(struct inode *inode, int mask)
u32 perms;
bool from_access;
unsigned flags = mask & MAY_NOT_BLOCK;
struct inode_security_struct *isec;
u32 sid;
struct av_decision avd;
int rc, rc2;
u32 audited, denied;
from_access = mask & MAY_ACCESS;
mask &= (MAY_READ|MAY_WRITE|MAY_EXEC|MAY_APPEND);
@ -2692,6 +2697,23 @@ static int selinux_inode_permission(struct inode *inode, int mask)
if (!mask)
return 0;
validate_creds(cred);
if (unlikely(IS_PRIVATE(inode)))
return 0;
perms = file_mask_to_av(inode->i_mode, mask);
sid = cred_sid(cred);
isec = inode->i_security;
rc = avc_has_perm_noaudit(sid, isec->sid, isec->sclass, perms, 0, &avd);
audited = avc_audit_required(perms, &avd, rc,
from_access ? FILE__AUDIT_ACCESS : 0,
&denied);
if (likely(!audited))
return rc;
COMMON_AUDIT_DATA_INIT(&ad, INODE);
ad.selinux_audit_data = &sad;
ad.u.inode = inode;
@ -2699,9 +2721,11 @@ static int selinux_inode_permission(struct inode *inode, int mask)
if (from_access)
ad.selinux_audit_data->auditdeny |= FILE__AUDIT_ACCESS;
perms = file_mask_to_av(inode->i_mode, mask);
return inode_has_perm(cred, inode, perms, &ad, flags);
rc2 = slow_avc_audit(sid, isec->sid, isec->sclass, perms,
audited, denied, &ad, flags);
if (rc2)
return rc2;
return rc;
}
static int selinux_inode_setattr(struct dentry *dentry, struct iattr *iattr)

View File

@ -77,11 +77,83 @@ struct selinux_audit_data {
void __init avc_init(void);
int avc_audit(u32 ssid, u32 tsid,
static inline u32 avc_audit_required(u32 requested,
struct av_decision *avd,
int result,
u32 auditdeny,
u32 *deniedp)
{
u32 denied, audited;
denied = requested & ~avd->allowed;
if (unlikely(denied)) {
audited = denied & avd->auditdeny;
/*
* auditdeny is TRICKY! Setting a bit in
* this field means that ANY denials should NOT be audited if
* the policy contains an explicit dontaudit rule for that
* permission. Take notice that this is unrelated to the
* actual permissions that were denied. As an example lets
* assume:
*
* denied == READ
* avd.auditdeny & ACCESS == 0 (not set means explicit rule)
* auditdeny & ACCESS == 1
*
* We will NOT audit the denial even though the denied
* permission was READ and the auditdeny checks were for
* ACCESS
*/
if (auditdeny && !(auditdeny & avd->auditdeny))
audited = 0;
} else if (result)
audited = denied = requested;
else
audited = requested & avd->auditallow;
*deniedp = denied;
return audited;
}
int slow_avc_audit(u32 ssid, u32 tsid, u16 tclass,
u32 requested, u32 audited, u32 denied,
struct common_audit_data *a,
unsigned flags);
/**
* avc_audit - Audit the granting or denial of permissions.
* @ssid: source security identifier
* @tsid: target security identifier
* @tclass: target security class
* @requested: requested permissions
* @avd: access vector decisions
* @result: result from avc_has_perm_noaudit
* @a: auxiliary audit data
* @flags: VFS walk flags
*
* Audit the granting or denial of permissions in accordance
* with the policy. This function is typically called by
* avc_has_perm() after a permission check, but can also be
* called directly by callers who use avc_has_perm_noaudit()
* in order to separate the permission check from the auditing.
* For example, this separation is useful when the permission check must
* be performed under a lock, to allow the lock to be released
* before calling the auditing code.
*/
static inline int avc_audit(u32 ssid, u32 tsid,
u16 tclass, u32 requested,
struct av_decision *avd,
int result,
struct common_audit_data *a, unsigned flags);
struct common_audit_data *a, unsigned flags)
{
u32 audited, denied;
audited = avc_audit_required(requested, avd, result,
a ? a->selinux_audit_data->auditdeny : 0,
&denied);
if (likely(!audited))
return 0;
return slow_avc_audit(ssid, tsid, tclass,
requested, audited, denied,
a, flags);
}
#define AVC_STRICT 1 /* Ignore permissive mode. */
int avc_has_perm_noaudit(u32 ssid, u32 tsid,