Smack: Fix possible NULL pointer dereference at smk_netlbl_mls()
[platform/kernel/kernel-mfld-blackbay.git] / security / smack / smack_access.c
index 9637e10..bd8338f 100644 (file)
 struct smack_known smack_known_huh = {
        .smk_known      = "?",
        .smk_secid      = 2,
-       .smk_cipso      = NULL,
 };
 
 struct smack_known smack_known_hat = {
        .smk_known      = "^",
        .smk_secid      = 3,
-       .smk_cipso      = NULL,
 };
 
 struct smack_known smack_known_star = {
        .smk_known      = "*",
        .smk_secid      = 4,
-       .smk_cipso      = NULL,
 };
 
 struct smack_known smack_known_floor = {
        .smk_known      = "_",
        .smk_secid      = 5,
-       .smk_cipso      = NULL,
 };
 
 struct smack_known smack_known_invalid = {
        .smk_known      = "",
        .smk_secid      = 6,
-       .smk_cipso      = NULL,
 };
 
 struct smack_known smack_known_web = {
        .smk_known      = "@",
        .smk_secid      = 7,
-       .smk_cipso      = NULL,
 };
 
 LIST_HEAD(smack_known_list);
@@ -77,14 +71,19 @@ int log_policy = SMACK_AUDIT_DENIED;
  * entry is found returns -ENOENT.
  *
  * NOTE:
- * Even though Smack labels are usually shared on smack_list
- * labels that come in off the network can't be imported
- * and added to the list for locking reasons.
  *
- * Therefore, it is necessary to check the contents of the labels,
- * not just the pointer values. Of course, in most cases the labels
- * will be on the list, so checking the pointers may be a worthwhile
- * optimization.
+ * Earlier versions of this function allowed for labels that
+ * were not on the label list. This was done to allow for
+ * labels to come over the network that had never been seen
+ * before on this host. Unless the receiving socket has the
+ * star label this will always result in a failure check. The
+ * star labeled socket case is now handled in the networking
+ * hooks so there is no case where the label is not on the
+ * label list. Checking to see if the address of two labels
+ * is the same is now a reliable test.
+ *
+ * Do the object check first because that is more
+ * likely to differ.
  */
 int smk_access_entry(char *subject_label, char *object_label,
                        struct list_head *rule_list)
@@ -93,13 +92,10 @@ int smk_access_entry(char *subject_label, char *object_label,
        struct smack_rule *srp;
 
        list_for_each_entry_rcu(srp, rule_list, list) {
-               if (srp->smk_subject == subject_label ||
-                   strcmp(srp->smk_subject, subject_label) == 0) {
-                       if (srp->smk_object == object_label ||
-                           strcmp(srp->smk_object, object_label) == 0) {
-                               may = srp->smk_access;
-                               break;
-                       }
+               if (srp->smk_object == object_label &&
+                   srp->smk_subject == subject_label) {
+                       may = srp->smk_access;
+                       break;
                }
        }
 
@@ -117,18 +113,12 @@ int smk_access_entry(char *subject_label, char *object_label,
  * access rule list and returns 0 if the access is permitted,
  * non zero otherwise.
  *
- * Even though Smack labels are usually shared on smack_list
- * labels that come in off the network can't be imported
- * and added to the list for locking reasons.
- *
- * Therefore, it is necessary to check the contents of the labels,
- * not just the pointer values. Of course, in most cases the labels
- * will be on the list, so checking the pointers may be a worthwhile
- * optimization.
+ * Smack labels are shared on smack_list
  */
 int smk_access(char *subject_label, char *object_label, int request,
               struct smk_audit_info *a)
 {
+       struct smack_known *skp;
        int may = MAY_NOT;
        int rc = 0;
 
@@ -137,8 +127,7 @@ int smk_access(char *subject_label, char *object_label, int request,
         *
         * A star subject can't access any object.
         */
-       if (subject_label == smack_known_star.smk_known ||
-           strcmp(subject_label, smack_known_star.smk_known) == 0) {
+       if (subject_label == smack_known_star.smk_known) {
                rc = -EACCES;
                goto out_audit;
        }
@@ -148,33 +137,27 @@ int smk_access(char *subject_label, char *object_label, int request,
         * An internet subject can access any object.
         */
        if (object_label == smack_known_web.smk_known ||
-           subject_label == smack_known_web.smk_known ||
-           strcmp(object_label, smack_known_web.smk_known) == 0 ||
-           strcmp(subject_label, smack_known_web.smk_known) == 0)
+           subject_label == smack_known_web.smk_known)
                goto out_audit;
        /*
         * A star object can be accessed by any subject.
         */
-       if (object_label == smack_known_star.smk_known ||
-           strcmp(object_label, smack_known_star.smk_known) == 0)
+       if (object_label == smack_known_star.smk_known)
                goto out_audit;
        /*
         * An object can be accessed in any way by a subject
         * with the same label.
         */
-       if (subject_label == object_label ||
-           strcmp(subject_label, object_label) == 0)
+       if (subject_label == object_label)
                goto out_audit;
        /*
         * A hat subject can read any object.
         * A floor object can be read by any subject.
         */
        if ((request & MAY_ANYREAD) == request) {
-               if (object_label == smack_known_floor.smk_known ||
-                   strcmp(object_label, smack_known_floor.smk_known) == 0)
+               if (object_label == smack_known_floor.smk_known)
                        goto out_audit;
-               if (subject_label == smack_known_hat.smk_known ||
-                   strcmp(subject_label, smack_known_hat.smk_known) == 0)
+               if (subject_label == smack_known_hat.smk_known)
                        goto out_audit;
        }
        /*
@@ -184,8 +167,9 @@ int smk_access(char *subject_label, char *object_label, int request,
         * good. A negative response from smk_access_entry()
         * indicates there is no entry for this pair.
         */
+       skp = smk_find_entry(subject_label);
        rcu_read_lock();
-       may = smk_access_entry(subject_label, object_label, &smack_rule_list);
+       may = smk_access_entry(subject_label, object_label, &skp->smk_rules);
        rcu_read_unlock();
 
        if (may > 0 && (request & may) == request)
@@ -236,14 +220,9 @@ int smk_curacc(char *obj_label, u32 mode, struct smk_audit_info *a)
        }
 
        /*
-        * Return if a specific label has been designated as the
-        * only one that gets privilege and current does not
-        * have that label.
+        * Allow for priviliged to override policy.
         */
-       if (smack_onlycap != NULL && smack_onlycap != sp)
-               goto out_audit;
-
-       if (capable(CAP_MAC_OVERRIDE))
+       if (rc != 0 && smack_privileged(CAP_MAC_OVERRIDE))
                rc = 0;
 
 out_audit:
@@ -273,6 +252,8 @@ static inline void smack_str_from_perm(char *string, int access)
                string[i++] = 'x';
        if (access & MAY_APPEND)
                string[i++] = 'a';
+       if (access & MAY_TRANSMUTE)
+               string[i++] = 't';
        string[i] = '\0';
 }
 /**
@@ -341,7 +322,104 @@ void smack_log(char *subject_label, char *object_label, int request,
 }
 #endif
 
-static DEFINE_MUTEX(smack_known_lock);
+DEFINE_MUTEX(smack_known_lock);
+
+/**
+ * smk_find_entry - find a label on the list, return the list entry
+ * @string: a text string that might be a Smack label
+ *
+ * Returns a pointer to the entry in the label list that
+ * matches the passed string.
+ */
+struct smack_known *smk_find_entry(const char *string)
+{
+       struct smack_known *skp;
+
+       list_for_each_entry_rcu(skp, &smack_known_list, list) {
+               if (strcmp(skp->smk_known, string) == 0)
+                       return skp;
+       }
+
+       return NULL;
+}
+
+/**
+ * smk_parse_smack - parse smack label from a text string
+ * @string: a text string that might contain a Smack label
+ * @len: the maximum size, or zero if it is NULL terminated.
+ *
+ * Returns a pointer to the clean label, or NULL
+ */
+char *smk_parse_smack(const char *string, int len)
+{
+       char *smack;
+       int i;
+
+       if (len <= 0)
+               len = strlen(string) + 1;
+
+       /*
+        * Reserve a leading '-' as an indicator that
+        * this isn't a label, but an option to interfaces
+        * including /smack/cipso and /smack/cipso2
+        */
+       if (string[0] == '-')
+               return NULL;
+
+       for (i = 0; i < len; i++)
+               if (string[i] > '~' || string[i] <= ' ' || string[i] == '/' ||
+                   string[i] == '"' || string[i] == '\\' || string[i] == '\'')
+                       break;
+
+       if (i == 0 || i >= SMK_LONGLABEL)
+               return NULL;
+
+       smack = kzalloc(i + 1, GFP_KERNEL);
+       if (smack != NULL) {
+               strncpy(smack, string, i + 1);
+               smack[i] = '\0';
+       }
+       return smack;
+}
+
+/**
+ * smk_netlbl_mls - convert a catset to netlabel mls categories
+ * @catset: the Smack categories
+ * @sap: where to put the netlabel categories
+ *
+ * Allocates and fills attr.mls
+ * Returns 0 on success, error code on failure.
+ */
+int smk_netlbl_mls(int level, char *catset, struct netlbl_lsm_secattr *sap,
+                       int len)
+{
+       unsigned char *cp;
+       unsigned char m;
+       int cat;
+       int rc;
+       int byte;
+
+       sap->flags |= NETLBL_SECATTR_MLS_CAT;
+       sap->attr.mls.lvl = level;
+       sap->attr.mls.cat = netlbl_secattr_catmap_alloc(GFP_ATOMIC);
+       if (!sap->attr.mls.cat)
+               return -ENOMEM;
+       sap->attr.mls.cat->startbit = 0;
+
+       for (cat = 1, cp = catset, byte = 0; byte < len; cp++, byte++)
+               for (m = 0x80; m != 0; m >>= 1, cat++) {
+                       if ((m & *cp) == 0)
+                               continue;
+                       rc = netlbl_secattr_catmap_setbit(sap->attr.mls.cat,
+                                                         cat, GFP_ATOMIC);
+                       if (rc < 0) {
+                               netlbl_secattr_catmap_free(sap->attr.mls.cat);
+                               return rc;
+                       }
+               }
+
+       return 0;
+}
 
 /**
  * smk_import_entry - import a label, return the list entry
@@ -354,53 +432,59 @@ static DEFINE_MUTEX(smack_known_lock);
 struct smack_known *smk_import_entry(const char *string, int len)
 {
        struct smack_known *skp;
-       char smack[SMK_LABELLEN];
-       int found;
-       int i;
-
-       if (len <= 0 || len > SMK_MAXLEN)
-               len = SMK_MAXLEN;
-
-       for (i = 0, found = 0; i < SMK_LABELLEN; i++) {
-               if (found)
-                       smack[i] = '\0';
-               else if (i >= len || string[i] > '~' || string[i] <= ' ' ||
-                        string[i] == '/' || string[i] == '"' ||
-                        string[i] == '\\' || string[i] == '\'') {
-                       smack[i] = '\0';
-                       found = 1;
-               } else
-                       smack[i] = string[i];
-       }
+       char *smack;
+       int slen;
+       int rc;
 
-       if (smack[0] == '\0')
+       smack = smk_parse_smack(string, len);
+       if (smack == NULL)
                return NULL;
 
        mutex_lock(&smack_known_lock);
 
-       found = 0;
-       list_for_each_entry_rcu(skp, &smack_known_list, list) {
-               if (strncmp(skp->smk_known, smack, SMK_MAXLEN) == 0) {
-                       found = 1;
-                       break;
-               }
-       }
+       skp = smk_find_entry(smack);
+       if (skp != NULL)
+               goto freeout;
 
-       if (found == 0) {
-               skp = kzalloc(sizeof(struct smack_known), GFP_KERNEL);
-               if (skp != NULL) {
-                       strncpy(skp->smk_known, smack, SMK_MAXLEN);
-                       skp->smk_secid = smack_next_secid++;
-                       skp->smk_cipso = NULL;
-                       spin_lock_init(&skp->smk_cipsolock);
-                       /*
-                        * Make sure that the entry is actually
-                        * filled before putting it on the list.
-                        */
-                       list_add_rcu(&skp->list, &smack_known_list);
-               }
-       }
+       skp = kzalloc(sizeof(*skp), GFP_KERNEL);
+       if (skp == NULL)
+               goto freeout;
 
+       skp->smk_known = smack;
+       skp->smk_secid = smack_next_secid++;
+       skp->smk_netlabel.domain = skp->smk_known;
+       skp->smk_netlabel.flags =
+               NETLBL_SECATTR_DOMAIN | NETLBL_SECATTR_MLS_LVL;
+       /*
+        * If direct labeling works use it.
+        * Otherwise use mapped labeling.
+        */
+       slen = strlen(smack);
+       if (slen < SMK_CIPSOLEN)
+               rc = smk_netlbl_mls(smack_cipso_direct, skp->smk_known,
+                              &skp->smk_netlabel, slen);
+       else
+               rc = smk_netlbl_mls(smack_cipso_mapped, (char *)&skp->smk_secid,
+                              &skp->smk_netlabel, sizeof(skp->smk_secid));
+
+       if (rc >= 0) {
+               INIT_LIST_HEAD(&skp->smk_rules);
+               mutex_init(&skp->smk_rules_lock);
+               /*
+                * Make sure that the entry is actually
+                * filled before putting it on the list.
+                */
+               list_add_rcu(&skp->list, &smack_known_list);
+               goto unlockout;
+       }
+       /*
+        * smk_netlbl_mls failed.
+        */
+       kfree(skp);
+       skp = NULL;
+freeout:
+       kfree(smack);
+unlockout:
        mutex_unlock(&smack_known_lock);
 
        return skp;
@@ -463,85 +547,9 @@ char *smack_from_secid(const u32 secid)
  */
 u32 smack_to_secid(const char *smack)
 {
-       struct smack_known *skp;
+       struct smack_known *skp = smk_find_entry(smack);
 
-       rcu_read_lock();
-       list_for_each_entry_rcu(skp, &smack_known_list, list) {
-               if (strncmp(skp->smk_known, smack, SMK_MAXLEN) == 0) {
-                       rcu_read_unlock();
-                       return skp->smk_secid;
-               }
-       }
-       rcu_read_unlock();
-       return 0;
-}
-
-/**
- * smack_from_cipso - find the Smack label associated with a CIPSO option
- * @level: Bell & LaPadula level from the network
- * @cp: Bell & LaPadula categories from the network
- * @result: where to put the Smack value
- *
- * This is a simple lookup in the label table.
- *
- * This is an odd duck as far as smack handling goes in that
- * it sends back a copy of the smack label rather than a pointer
- * to the master list. This is done because it is possible for
- * a foreign host to send a smack label that is new to this
- * machine and hence not on the list. That would not be an
- * issue except that adding an entry to the master list can't
- * be done at that point.
- */
-void smack_from_cipso(u32 level, char *cp, char *result)
-{
-       struct smack_known *kp;
-       char *final = NULL;
-
-       rcu_read_lock();
-       list_for_each_entry(kp, &smack_known_list, list) {
-               if (kp->smk_cipso == NULL)
-                       continue;
-
-               spin_lock_bh(&kp->smk_cipsolock);
-
-               if (kp->smk_cipso->smk_level == level &&
-                   memcmp(kp->smk_cipso->smk_catset, cp, SMK_LABELLEN) == 0)
-                       final = kp->smk_known;
-
-               spin_unlock_bh(&kp->smk_cipsolock);
-       }
-       rcu_read_unlock();
-       if (final == NULL)
-               final = smack_known_huh.smk_known;
-       strncpy(result, final, SMK_MAXLEN);
-       return;
-}
-
-/**
- * smack_to_cipso - find the CIPSO option to go with a Smack label
- * @smack: a pointer to the smack label in question
- * @cp: where to put the result
- *
- * Returns zero if a value is available, non-zero otherwise.
- */
-int smack_to_cipso(const char *smack, struct smack_cipso *cp)
-{
-       struct smack_known *kp;
-       int found = 0;
-
-       rcu_read_lock();
-       list_for_each_entry_rcu(kp, &smack_known_list, list) {
-               if (kp->smk_known == smack ||
-                   strcmp(kp->smk_known, smack) == 0) {
-                       found = 1;
-                       break;
-               }
-       }
-       rcu_read_unlock();
-
-       if (found == 0 || kp->smk_cipso == NULL)
-               return -ENOENT;
-
-       memcpy(cp, kp->smk_cipso, sizeof(struct smack_cipso));
-       return 0;
+       if (skp == NULL)
+               return 0;
+       return skp->smk_secid;
 }