]> git.karo-electronics.de Git - karo-tx-linux.git/blobdiff - security/smack/smackfs.c
Merge remote-tracking branch 'kvm-ppc/kvm-ppc-next'
[karo-tx-linux.git] / security / smack / smackfs.c
index 76a5dca46404bd62928b34a492d0534e986a34d8..2479a41a7dff3e34be79ce7f46031a18119a9515 100644 (file)
@@ -50,12 +50,12 @@ enum smk_inos {
        SMK_ACCESS2     = 16,   /* make an access check with long labels */
        SMK_CIPSO2      = 17,   /* load long label -> CIPSO mapping */
        SMK_REVOKE_SUBJ = 18,   /* set rules with subject label to '-' */
+       SMK_CHANGE_RULE = 19,   /* change or add rules (long labels) */
 };
 
 /*
  * List locks
  */
-static DEFINE_MUTEX(smack_list_lock);
 static DEFINE_MUTEX(smack_cipso_lock);
 static DEFINE_MUTEX(smack_ambient_lock);
 static DEFINE_MUTEX(smk_netlbladdr_lock);
@@ -110,6 +110,13 @@ struct smack_master_list {
 
 LIST_HEAD(smack_rule_list);
 
+struct smack_parsed_rule {
+       char                    *smk_subject;
+       char                    *smk_object;
+       int                     smk_access1;
+       int                     smk_access2;
+};
+
 static int smk_cipso_doi_value = SMACK_CIPSO_DOI_DEFAULT;
 
 const char *smack_cipso_option = SMACK_CIPSO_OPTION;
@@ -167,25 +174,28 @@ static void smk_netlabel_audit_set(struct netlbl_audit *nap)
 #define SMK_NETLBLADDRMIN      9
 
 /**
- * smk_set_access - add a rule to the rule list
- * @srp: the new rule to add
+ * smk_set_access - add a rule to the rule list or replace an old rule
+ * @srp: the rule to add or replace
  * @rule_list: the list of rules
  * @rule_lock: the rule list lock
+ * @global: if non-zero, indicates a global rule
  *
  * Looks through the current subject/object/access list for
  * the subject/object pair and replaces the access that was
  * there. If the pair isn't found add it with the specified
  * access.
  *
- * Returns 1 if a rule was found to exist already, 0 if it is new
  * Returns 0 if nothing goes wrong or -ENOMEM if it fails
  * during the allocation of the new pair to add.
  */
-static int smk_set_access(struct smack_rule *srp, struct list_head *rule_list,
-                               struct mutex *rule_lock)
+static int smk_set_access(struct smack_parsed_rule *srp,
+                               struct list_head *rule_list,
+                               struct mutex *rule_lock, int global)
 {
        struct smack_rule *sp;
+       struct smack_master_list *smlp;
        int found = 0;
+       int rc = 0;
 
        mutex_lock(rule_lock);
 
@@ -197,23 +207,89 @@ static int smk_set_access(struct smack_rule *srp, struct list_head *rule_list,
                if (sp->smk_object == srp->smk_object &&
                    sp->smk_subject == srp->smk_subject) {
                        found = 1;
-                       sp->smk_access = srp->smk_access;
+                       sp->smk_access |= srp->smk_access1;
+                       sp->smk_access &= ~srp->smk_access2;
                        break;
                }
        }
-       if (found == 0)
-               list_add_rcu(&srp->list, rule_list);
 
+       if (found == 0) {
+               sp = kzalloc(sizeof(*sp), GFP_KERNEL);
+               if (sp == NULL) {
+                       rc = -ENOMEM;
+                       goto out;
+               }
+
+               sp->smk_subject = srp->smk_subject;
+               sp->smk_object = srp->smk_object;
+               sp->smk_access = srp->smk_access1 & ~srp->smk_access2;
+
+               list_add_rcu(&sp->list, rule_list);
+               /*
+                * If this is a global as opposed to self and a new rule
+                * it needs to get added for reporting.
+                */
+               if (global) {
+                       smlp = kzalloc(sizeof(*smlp), GFP_KERNEL);
+                       if (smlp != NULL) {
+                               smlp->smk_rule = sp;
+                               list_add_rcu(&smlp->list, &smack_rule_list);
+                       } else
+                               rc = -ENOMEM;
+               }
+       }
+
+out:
        mutex_unlock(rule_lock);
+       return rc;
+}
+
+/**
+ * smk_perm_from_str - parse smack accesses from a text string
+ * @string: a text string that contains a Smack accesses code
+ *
+ * Returns an integer with respective bits set for specified accesses.
+ */
+static int smk_perm_from_str(const char *string)
+{
+       int perm = 0;
+       const char *cp;
 
-       return found;
+       for (cp = string; ; cp++)
+               switch (*cp) {
+               case '-':
+                       break;
+               case 'r':
+               case 'R':
+                       perm |= MAY_READ;
+                       break;
+               case 'w':
+               case 'W':
+                       perm |= MAY_WRITE;
+                       break;
+               case 'x':
+               case 'X':
+                       perm |= MAY_EXEC;
+                       break;
+               case 'a':
+               case 'A':
+                       perm |= MAY_APPEND;
+                       break;
+               case 't':
+               case 'T':
+                       perm |= MAY_TRANSMUTE;
+                       break;
+               default:
+                       return perm;
+               }
 }
 
 /**
  * smk_fill_rule - Fill Smack rule from strings
  * @subject: subject label string
  * @object: object label string
- * @access: access string
+ * @access1: access string
+ * @access2: string with permissions to be removed
  * @rule: Smack rule
  * @import: if non-zero, import labels
  * @len: label length limit
@@ -221,8 +297,9 @@ static int smk_set_access(struct smack_rule *srp, struct list_head *rule_list,
  * Returns 0 on success, -1 on failure
  */
 static int smk_fill_rule(const char *subject, const char *object,
-                               const char *access, struct smack_rule *rule,
-                               int import, int len)
+                               const char *access1, const char *access2,
+                               struct smack_parsed_rule *rule, int import,
+                               int len)
 {
        const char *cp;
        struct smack_known *skp;
@@ -255,36 +332,11 @@ static int smk_fill_rule(const char *subject, const char *object,
                rule->smk_object = skp->smk_known;
        }
 
-       rule->smk_access = 0;
-
-       for (cp = access; *cp != '\0'; cp++) {
-               switch (*cp) {
-               case '-':
-                       break;
-               case 'r':
-               case 'R':
-                       rule->smk_access |= MAY_READ;
-                       break;
-               case 'w':
-               case 'W':
-                       rule->smk_access |= MAY_WRITE;
-                       break;
-               case 'x':
-               case 'X':
-                       rule->smk_access |= MAY_EXEC;
-                       break;
-               case 'a':
-               case 'A':
-                       rule->smk_access |= MAY_APPEND;
-                       break;
-               case 't':
-               case 'T':
-                       rule->smk_access |= MAY_TRANSMUTE;
-                       break;
-               default:
-                       return 0;
-               }
-       }
+       rule->smk_access1 = smk_perm_from_str(access1);
+       if (access2)
+               rule->smk_access2 = smk_perm_from_str(access2);
+       else
+               rule->smk_access2 = ~rule->smk_access1;
 
        return 0;
 }
@@ -297,30 +349,33 @@ static int smk_fill_rule(const char *subject, const char *object,
  *
  * Returns 0 on success, -1 on errors.
  */
-static int smk_parse_rule(const char *data, struct smack_rule *rule, int import)
+static int smk_parse_rule(const char *data, struct smack_parsed_rule *rule,
+                               int import)
 {
        int rc;
 
        rc = smk_fill_rule(data, data + SMK_LABELLEN,
-                          data + SMK_LABELLEN + SMK_LABELLEN, rule, import,
-                          SMK_LABELLEN);
+                          data + SMK_LABELLEN + SMK_LABELLEN, NULL, rule,
+                          import, SMK_LABELLEN);
        return rc;
 }
 
 /**
  * smk_parse_long_rule - parse Smack rule from rule string
  * @data: string to be parsed, null terminated
- * @rule: Smack rule
+ * @rule: Will be filled with Smack parsed rule
  * @import: if non-zero, import labels
+ * @change: if non-zero, data is from /smack/change-rule
  *
  * Returns 0 on success, -1 on failure
  */
-static int smk_parse_long_rule(const char *data, struct smack_rule *rule,
-                               int import)
+static int smk_parse_long_rule(const char *data, struct smack_parsed_rule *rule,
+                               int import, int change)
 {
        char *subject;
        char *object;
-       char *access;
+       char *access1;
+       char *access2;
        int datalen;
        int rc = -1;
 
@@ -334,14 +389,27 @@ static int smk_parse_long_rule(const char *data, struct smack_rule *rule,
        object = kzalloc(datalen, GFP_KERNEL);
        if (object == NULL)
                goto free_out_s;
-       access = kzalloc(datalen, GFP_KERNEL);
-       if (access == NULL)
+       access1 = kzalloc(datalen, GFP_KERNEL);
+       if (access1 == NULL)
                goto free_out_o;
+       access2 = kzalloc(datalen, GFP_KERNEL);
+       if (access2 == NULL)
+               goto free_out_a;
+
+       if (change) {
+               if (sscanf(data, "%s %s %s %s",
+                       subject, object, access1, access2) == 4)
+                       rc = smk_fill_rule(subject, object, access1, access2,
+                               rule, import, 0);
+       } else {
+               if (sscanf(data, "%s %s %s", subject, object, access1) == 3)
+                       rc = smk_fill_rule(subject, object, access1, NULL,
+                               rule, import, 0);
+       }
 
-       if (sscanf(data, "%s %s %s", subject, object, access) == 3)
-               rc = smk_fill_rule(subject, object, access, rule, import, 0);
-
-       kfree(access);
+       kfree(access2);
+free_out_a:
+       kfree(access1);
 free_out_o:
        kfree(object);
 free_out_s:
@@ -351,6 +419,7 @@ free_out_s:
 
 #define SMK_FIXED24_FMT        0       /* Fixed 24byte label format */
 #define SMK_LONG_FMT   1       /* Variable long label format */
+#define SMK_CHANGE_FMT 2       /* Rule modification format */
 /**
  * smk_write_rules_list - write() for any /smack rule file
  * @file: file pointer, not actually used
@@ -359,22 +428,24 @@ free_out_s:
  * @ppos: where to start - must be 0
  * @rule_list: the list of rules to write to
  * @rule_lock: lock for the rule list
- * @format: /smack/load or /smack/load2 format.
+ * @format: /smack/load or /smack/load2 or /smack/change-rule format.
  *
  * Get one smack access rule from above.
  * The format for SMK_LONG_FMT is:
  *     "subject<whitespace>object<whitespace>access[<whitespace>...]"
  * The format for SMK_FIXED24_FMT is exactly:
  *     "subject                 object                  rwxat"
+ * The format for SMK_CHANGE_FMT is:
+ *     "subject<whitespace>object<whitespace>
+ *      acc_enable<whitespace>acc_disable[<whitespace>...]"
  */
 static ssize_t smk_write_rules_list(struct file *file, const char __user *buf,
                                        size_t count, loff_t *ppos,
                                        struct list_head *rule_list,
                                        struct mutex *rule_lock, int format)
 {
-       struct smack_master_list *smlp;
        struct smack_known *skp;
-       struct smack_rule *rule;
+       struct smack_parsed_rule *rule;
        char *data;
        int datalen;
        int rc = -EINVAL;
@@ -417,7 +488,11 @@ static ssize_t smk_write_rules_list(struct file *file, const char __user *buf,
                 * Be sure the data string is terminated.
                 */
                data[count] = '\0';
-               if (smk_parse_long_rule(data, rule, 1))
+               if (smk_parse_long_rule(data, rule, 1, 0))
+                       goto out_free_rule;
+       } else if (format == SMK_CHANGE_FMT) {
+               data[count] = '\0';
+               if (smk_parse_long_rule(data, rule, 1, 1))
                        goto out_free_rule;
        } else {
                /*
@@ -437,22 +512,9 @@ static ssize_t smk_write_rules_list(struct file *file, const char __user *buf,
                rule_lock = &skp->smk_rules_lock;
        }
 
-       rc = count;
-       /*
-        * If this is a global as opposed to self and a new rule
-        * it needs to get added for reporting.
-        * smk_set_access returns true if there was already a rule
-        * for the subject/object pair, and false if it was new.
-        */
-       if (!smk_set_access(rule, rule_list, rule_lock)) {
-               if (load) {
-                       smlp = kzalloc(sizeof(*smlp), GFP_KERNEL);
-                       if (smlp != NULL) {
-                               smlp->smk_rule = rule;
-                               list_add_rcu(&smlp->list, &smack_rule_list);
-                       } else
-                               rc = -ENOMEM;
-               }
+       rc = smk_set_access(rule, rule_list, rule_lock, load);
+       if (rc == 0) {
+               rc = count;
                goto out;
        }
 
@@ -1774,7 +1836,7 @@ static const struct file_operations smk_load_self_ops = {
 static ssize_t smk_user_access(struct file *file, const char __user *buf,
                                size_t count, loff_t *ppos, int format)
 {
-       struct smack_rule rule;
+       struct smack_parsed_rule rule;
        char *data;
        char *cod;
        int res;
@@ -1796,14 +1858,14 @@ static ssize_t smk_user_access(struct file *file, const char __user *buf,
                        return -ENOMEM;
                memcpy(cod, data, count);
                cod[count] = '\0';
-               res = smk_parse_long_rule(cod, &rule, 0);
+               res = smk_parse_long_rule(cod, &rule, 0, 0);
                kfree(cod);
        }
 
        if (res)
                return -EINVAL;
 
-       res = smk_access(rule.smk_subject, rule.smk_object, rule.smk_access,
+       res = smk_access(rule.smk_subject, rule.smk_object, rule.smk_access1,
                          NULL);
        data[0] = res == 0 ? '1' : '0';
        data[1] = '\0';
@@ -2035,10 +2097,8 @@ static ssize_t smk_write_revoke_subj(struct file *file, const char __user *buf,
        }
 
        skp = smk_find_entry(cp);
-       if (skp == NULL) {
-               rc = -EINVAL;
+       if (skp == NULL)
                goto free_out;
-       }
 
        rule_list = &skp->smk_rules;
        rule_lock = &skp->smk_rules_lock;
@@ -2076,6 +2136,33 @@ static int smk_init_sysfs(void)
        return 0;
 }
 
+/**
+ * smk_write_change_rule - write() for /smack/change-rule
+ * @file: file pointer
+ * @buf: data from user space
+ * @count: bytes sent
+ * @ppos: where to start - must be 0
+ */
+static ssize_t smk_write_change_rule(struct file *file, const char __user *buf,
+                               size_t count, loff_t *ppos)
+{
+       /*
+        * Must have privilege.
+        */
+       if (!capable(CAP_MAC_ADMIN))
+               return -EPERM;
+
+       return smk_write_rules_list(file, buf, count, ppos, NULL, NULL,
+                                   SMK_CHANGE_FMT);
+}
+
+static const struct file_operations smk_change_rule_ops = {
+       .write          = smk_write_change_rule,
+       .read           = simple_transaction_read,
+       .release        = simple_transaction_release,
+       .llseek         = generic_file_llseek,
+};
+
 /**
  * smk_fill_super - fill the /smackfs superblock
  * @sb: the empty superblock
@@ -2125,6 +2212,8 @@ static int smk_fill_super(struct super_block *sb, void *data, int silent)
                [SMK_REVOKE_SUBJ] = {
                        "revoke-subject", &smk_revoke_subj_ops,
                        S_IRUGO|S_IWUSR},
+               [SMK_CHANGE_RULE] = {
+                       "change-rule", &smk_change_rule_ops, S_IRUGO|S_IWUSR},
                /* last one */
                        {""}
        };