security: optimize avc_audit() common path
[linux-2.6.git] / security / selinux / avc.c
index 9da6420..6989472 100644 (file)
 #define AVC_CACHE_RECLAIM              16
 
 #ifdef CONFIG_SECURITY_SELINUX_AVC_STATS
-#define avc_cache_stats_incr(field)                            \
-do {                                                           \
-       per_cpu(avc_cache_stats, get_cpu()).field++;            \
-       put_cpu();                                              \
-} while (0)
+#define avc_cache_stats_incr(field)    this_cpu_inc(avc_cache_stats.field)
 #else
 #define avc_cache_stats_incr(field)    do {} while (0)
 #endif
@@ -347,11 +343,10 @@ static struct avc_node *avc_lookup(u32 ssid, u32 tsid, u16 tclass)
        node = avc_search_node(ssid, tsid, tclass);
 
        if (node)
-               avc_cache_stats_incr(hits);
-       else
-               avc_cache_stats_incr(misses);
+               return node;
 
-       return node;
+       avc_cache_stats_incr(misses);
+       return NULL;
 }
 
 static int avc_latest_notif_update(int seqno, int is_insert)
@@ -462,6 +457,42 @@ static void avc_audit_post_callback(struct audit_buffer *ab, void *a)
                           ad->selinux_audit_data.tclass);
 }
 
+/* This is the slow part of avc audit with big stack footprint */
+static noinline int slow_avc_audit(u32 ssid, u32 tsid, u16 tclass,
+               u32 requested, u32 audited, u32 denied,
+               struct av_decision *avd, struct common_audit_data *a,
+               unsigned flags)
+{
+       struct common_audit_data stack_data;
+
+       if (!a) {
+               a = &stack_data;
+               COMMON_AUDIT_DATA_INIT(a, NONE);
+       }
+
+       /*
+        * When in a RCU walk do the audit on the RCU retry.  This is because
+        * the collection of the dname in an inode audit message is not RCU
+        * safe.  Note this may drop some audits when the situation changes
+        * during retry. However this is logically just as if the operation
+        * happened a little later.
+        */
+       if ((a->type == LSM_AUDIT_DATA_INODE) &&
+           (flags & MAY_NOT_BLOCK))
+               return -ECHILD;
+
+       a->selinux_audit_data.tclass = tclass;
+       a->selinux_audit_data.requested = requested;
+       a->selinux_audit_data.ssid = ssid;
+       a->selinux_audit_data.tsid = tsid;
+       a->selinux_audit_data.audited = audited;
+       a->selinux_audit_data.denied = denied;
+       a->lsm_pre_audit = avc_audit_pre_callback;
+       a->lsm_post_audit = avc_audit_post_callback;
+       common_lsm_audit(a);
+       return 0;
+}
+
 /**
  * avc_audit - Audit the granting or denial of permissions.
  * @ssid: source security identifier
@@ -471,6 +502,7 @@ static void avc_audit_post_callback(struct audit_buffer *ab, void *a)
  * @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
@@ -481,14 +513,14 @@ static void avc_audit_post_callback(struct audit_buffer *ab, void *a)
  * be performed under a lock, to allow the lock to be released
  * before calling the auditing code.
  */
-void avc_audit(u32 ssid, u32 tsid,
+int avc_audit(u32 ssid, u32 tsid,
               u16 tclass, u32 requested,
-              struct av_decision *avd, int result, struct common_audit_data *a)
+              struct av_decision *avd, int result, struct common_audit_data *a,
+              unsigned flags)
 {
-       struct common_audit_data stack_data;
        u32 denied, audited;
        denied = requested & ~avd->allowed;
-       if (denied) {
+       if (unlikely(denied)) {
                audited = denied & avd->auditdeny;
                /*
                 * a->selinux_audit_data.auditdeny is TRICKY!  Setting a bit in
@@ -514,21 +546,12 @@ void avc_audit(u32 ssid, u32 tsid,
                audited = denied = requested;
        else
                audited = requested & avd->auditallow;
-       if (!audited)
-               return;
-       if (!a) {
-               a = &stack_data;
-               COMMON_AUDIT_DATA_INIT(a, NONE);
-       }
-       a->selinux_audit_data.tclass = tclass;
-       a->selinux_audit_data.requested = requested;
-       a->selinux_audit_data.ssid = ssid;
-       a->selinux_audit_data.tsid = tsid;
-       a->selinux_audit_data.audited = audited;
-       a->selinux_audit_data.denied = denied;
-       a->lsm_pre_audit = avc_audit_pre_callback;
-       a->lsm_post_audit = avc_audit_post_callback;
-       common_lsm_audit(a);
+       if (likely(!audited))
+               return 0;
+
+       return slow_avc_audit(ssid, tsid, tclass,
+               requested, audited, denied,
+               avd, a, flags);
 }
 
 /**
@@ -741,10 +764,9 @@ int avc_ss_reset(u32 seqno)
 int avc_has_perm_noaudit(u32 ssid, u32 tsid,
                         u16 tclass, u32 requested,
                         unsigned flags,
-                        struct av_decision *in_avd)
+                        struct av_decision *avd)
 {
        struct avc_node *node;
-       struct av_decision avd_entry, *avd;
        int rc = 0;
        u32 denied;
 
@@ -753,20 +775,13 @@ int avc_has_perm_noaudit(u32 ssid, u32 tsid,
        rcu_read_lock();
 
        node = avc_lookup(ssid, tsid, tclass);
-       if (!node) {
+       if (unlikely(!node)) {
                rcu_read_unlock();
-
-               if (in_avd)
-                       avd = in_avd;
-               else
-                       avd = &avd_entry;
-
                security_compute_av(ssid, tsid, tclass, avd);
                rcu_read_lock();
                node = avc_insert(ssid, tsid, tclass, avd);
        } else {
-               if (in_avd)
-                       memcpy(in_avd, &node->ae.avd, sizeof(*in_avd));
+               memcpy(avd, &node->ae.avd, sizeof(*avd));
                avd = &node->ae.avd;
        }
 
@@ -793,6 +808,7 @@ int avc_has_perm_noaudit(u32 ssid, u32 tsid,
  * @tclass: target security class
  * @requested: requested permissions, interpreted based on @tclass
  * @auditdata: auxiliary audit data
+ * @flags: VFS walk flags
  *
  * Check the AVC to determine whether the @requested permissions are granted
  * for the SID pair (@ssid, @tsid), interpreting the permissions
@@ -802,14 +818,19 @@ int avc_has_perm_noaudit(u32 ssid, u32 tsid,
  * permissions are granted, -%EACCES if any permissions are denied, or
  * another -errno upon other errors.
  */
-int avc_has_perm(u32 ssid, u32 tsid, u16 tclass,
-                u32 requested, struct common_audit_data *auditdata)
+int avc_has_perm_flags(u32 ssid, u32 tsid, u16 tclass,
+                      u32 requested, struct common_audit_data *auditdata,
+                      unsigned flags)
 {
        struct av_decision avd;
-       int rc;
+       int rc, rc2;
 
        rc = avc_has_perm_noaudit(ssid, tsid, tclass, requested, 0, &avd);
-       avc_audit(ssid, tsid, tclass, requested, &avd, rc, auditdata);
+
+       rc2 = avc_audit(ssid, tsid, tclass, requested, &avd, rc, auditdata,
+                       flags);
+       if (rc2)
+               return rc2;
        return rc;
 }