From 9777582e8d604f69ce3a93805065e451487e26b4 Mon Sep 17 00:00:00 2001 From: Seung-Woo Kim Date: Fri, 17 Apr 2015 15:25:04 +0900 Subject: [PATCH 1/4] Smack: ignore private inode for smack_file_receive The dmabuf fd can be shared between processes via unix domain socket. The file of dmabuf fd is came from anon_inode. The inode has no set and get xattr operations, so it can not be shared between processes with smack. This patch fixes just to ignore private inode including anon_inode for smack_file_receive. Signed-off-by: Seung-Woo Kim --- security/smack/smack_lsm.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c index 5eae42c8d0d5..f09b8c7cf1e7 100644 --- a/security/smack/smack_lsm.c +++ b/security/smack/smack_lsm.c @@ -1658,6 +1658,9 @@ static int smack_file_receive(struct file *file) struct smk_audit_info ad; struct inode *inode = file_inode(file); + if (unlikely(IS_PRIVATE(inode))) + return 0; + smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_PATH); smk_ad_setfield_u_fs_path(&ad, file->f_path); /* From e774ad683f425a51f87711164ea166d9dcc41477 Mon Sep 17 00:00:00 2001 From: Lukasz Pawelczyk Date: Mon, 20 Apr 2015 17:12:54 +0200 Subject: [PATCH 2/4] smack: pass error code through pointers This patch makes the following functions to use ERR_PTR() and related macros to pass the appropriate error code through returned pointers: smk_parse_smack() smk_import_entry() smk_fetch() It also makes all the other functions that use them to handle the error cases properly. This ways correct error codes from places where they happened can be propagated to the user space if necessary. Doing this it fixes a bug in onlycap and unconfined files handling. Previously their content was cleared on any error from smk_import_entry/smk_parse_smack, be it EINVAL (as originally intended) or ENOMEM. Right now it only reacts on EINVAL passing other codes properly to userspace. Comments have been updated accordingly. Signed-off-by: Lukasz Pawelczyk --- security/smack/smack_access.c | 27 ++++--- security/smack/smack_lsm.c | 93 +++++++++++++----------- security/smack/smackfs.c | 128 ++++++++++++++++++++-------------- 3 files changed, 145 insertions(+), 103 deletions(-) diff --git a/security/smack/smack_access.c b/security/smack/smack_access.c index 0f410fc56e33..408e20be1ad7 100644 --- a/security/smack/smack_access.c +++ b/security/smack/smack_access.c @@ -425,7 +425,7 @@ void smk_insert_entry(struct smack_known *skp) * @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. + * matches the passed string or NULL if not found. */ struct smack_known *smk_find_entry(const char *string) { @@ -448,7 +448,7 @@ struct smack_known *smk_find_entry(const char *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 + * Returns a pointer to the clean label or an error code. */ char *smk_parse_smack(const char *string, int len) { @@ -464,7 +464,7 @@ char *smk_parse_smack(const char *string, int len) * including /smack/cipso and /smack/cipso2 */ if (string[0] == '-') - return NULL; + return ERR_PTR(-EINVAL); for (i = 0; i < len; i++) if (string[i] > '~' || string[i] <= ' ' || string[i] == '/' || @@ -472,11 +472,13 @@ char *smk_parse_smack(const char *string, int len) break; if (i == 0 || i >= SMK_LONGLABEL) - return NULL; + return ERR_PTR(-EINVAL); smack = kzalloc(i + 1, GFP_KERNEL); - if (smack != NULL) - strncpy(smack, string, i); + if (smack == NULL) + return ERR_PTR(-ENOMEM); + + strncpy(smack, string, i); return smack; } @@ -523,7 +525,8 @@ int smk_netlbl_mls(int level, char *catset, struct netlbl_lsm_secattr *sap, * @len: the maximum size, or zero if it is NULL terminated. * * Returns a pointer to the entry in the label list that - * matches the passed string, adding it if necessary. + * matches the passed string, adding it if necessary, + * or an error code. */ struct smack_known *smk_import_entry(const char *string, int len) { @@ -533,8 +536,8 @@ struct smack_known *smk_import_entry(const char *string, int len) int rc; smack = smk_parse_smack(string, len); - if (smack == NULL) - return NULL; + if (IS_ERR(smack)) + return ERR_CAST(smack); mutex_lock(&smack_known_lock); @@ -543,8 +546,10 @@ struct smack_known *smk_import_entry(const char *string, int len) goto freeout; skp = kzalloc(sizeof(*skp), GFP_KERNEL); - if (skp == NULL) + if (skp == NULL) { + skp = ERR_PTR(-ENOMEM); goto freeout; + } skp->smk_known = smack; skp->smk_secid = smack_next_secid++; @@ -577,7 +582,7 @@ struct smack_known *smk_import_entry(const char *string, int len) * smk_netlbl_mls failed. */ kfree(skp); - skp = NULL; + skp = ERR_PTR(rc); freeout: kfree(smack); unlockout: diff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c index f09b8c7cf1e7..a143328f75eb 100644 --- a/security/smack/smack_lsm.c +++ b/security/smack/smack_lsm.c @@ -245,8 +245,8 @@ static int smk_bu_credfile(const struct cred *cred, struct file *file, * @ip: a pointer to the inode * @dp: a pointer to the dentry * - * Returns a pointer to the master list entry for the Smack label - * or NULL if there was no label to fetch. + * Returns a pointer to the master list entry for the Smack label, + * NULL if there was no label to fetch, or an error code. */ static struct smack_known *smk_fetch(const char *name, struct inode *ip, struct dentry *dp) @@ -256,14 +256,18 @@ static struct smack_known *smk_fetch(const char *name, struct inode *ip, struct smack_known *skp = NULL; if (ip->i_op->getxattr == NULL) - return NULL; + return ERR_PTR(-EOPNOTSUPP); buffer = kzalloc(SMK_LONGLABEL, GFP_KERNEL); if (buffer == NULL) - return NULL; + return ERR_PTR(-ENOMEM); rc = ip->i_op->getxattr(dp, name, buffer, SMK_LONGLABEL); - if (rc > 0) + if (rc < 0) + skp = ERR_PTR(rc); + else if (rc == 0) + skp = NULL; + else skp = smk_import_entry(buffer, rc); kfree(buffer); @@ -605,40 +609,44 @@ static int smack_sb_kern_mount(struct super_block *sb, int flags, void *data) if (strncmp(op, SMK_FSHAT, strlen(SMK_FSHAT)) == 0) { op += strlen(SMK_FSHAT); skp = smk_import_entry(op, 0); - if (skp != NULL) { - sp->smk_hat = skp; - specified = 1; - } + if (IS_ERR(skp)) + return PTR_ERR(skp); + sp->smk_hat = skp; + specified = 1; + } else if (strncmp(op, SMK_FSFLOOR, strlen(SMK_FSFLOOR)) == 0) { op += strlen(SMK_FSFLOOR); skp = smk_import_entry(op, 0); - if (skp != NULL) { - sp->smk_floor = skp; - specified = 1; - } + if (IS_ERR(skp)) + return PTR_ERR(skp); + sp->smk_floor = skp; + specified = 1; + } else if (strncmp(op, SMK_FSDEFAULT, strlen(SMK_FSDEFAULT)) == 0) { op += strlen(SMK_FSDEFAULT); skp = smk_import_entry(op, 0); - if (skp != NULL) { - sp->smk_default = skp; - specified = 1; - } + if (IS_ERR(skp)) + return PTR_ERR(skp); + sp->smk_default = skp; + specified = 1; + } else if (strncmp(op, SMK_FSROOT, strlen(SMK_FSROOT)) == 0) { op += strlen(SMK_FSROOT); skp = smk_import_entry(op, 0); - if (skp != NULL) { - sp->smk_root = skp; - specified = 1; - } + if (IS_ERR(skp)) + return PTR_ERR(skp); + sp->smk_root = skp; + specified = 1; + } else if (strncmp(op, SMK_FSTRANS, strlen(SMK_FSTRANS)) == 0) { op += strlen(SMK_FSTRANS); skp = smk_import_entry(op, 0); - if (skp != NULL) { - sp->smk_root = skp; - transmute = 1; - specified = 1; - } + if (IS_ERR(skp)) + return PTR_ERR(skp); + sp->smk_root = skp; + transmute = 1; + specified = 1; } } @@ -1118,7 +1126,9 @@ static int smack_inode_setxattr(struct dentry *dentry, const char *name, if (rc == 0 && check_import) { skp = size ? smk_import_entry(value, size) : NULL; - if (skp == NULL || (check_star && + if (IS_ERR(skp)) + rc = PTR_ERR(skp); + else if (skp == NULL || (check_star && (skp == &smack_known_star || skp == &smack_known_web))) rc = -EINVAL; } @@ -1158,19 +1168,19 @@ static void smack_inode_post_setxattr(struct dentry *dentry, const char *name, if (strcmp(name, XATTR_NAME_SMACK) == 0) { skp = smk_import_entry(value, size); - if (skp != NULL) + if (!IS_ERR(skp)) isp->smk_inode = skp; else isp->smk_inode = &smack_known_invalid; } else if (strcmp(name, XATTR_NAME_SMACKEXEC) == 0) { skp = smk_import_entry(value, size); - if (skp != NULL) + if (!IS_ERR(skp)) isp->smk_task = skp; else isp->smk_task = &smack_known_invalid; } else if (strcmp(name, XATTR_NAME_SMACKMMAP) == 0) { skp = smk_import_entry(value, size); - if (skp != NULL) + if (!IS_ERR(skp)) isp->smk_mmap = skp; else isp->smk_mmap = &smack_known_invalid; @@ -2403,8 +2413,8 @@ static int smack_inode_setsecurity(struct inode *inode, const char *name, return -EINVAL; skp = smk_import_entry(value, size); - if (skp == NULL) - return -EINVAL; + if (IS_ERR(skp)) + return PTR_ERR(skp); if (strcmp(name, XATTR_SMACK_SUFFIX) == 0) { nsp->smk_inode = skp; @@ -3177,7 +3187,7 @@ static void smack_d_instantiate(struct dentry *opt_dentry, struct inode *inode) */ dp = dget(opt_dentry); skp = smk_fetch(XATTR_NAME_SMACK, inode, dp); - if (skp != NULL) + if (!IS_ERR_OR_NULL(skp)) final = skp; /* @@ -3214,11 +3224,14 @@ static void smack_d_instantiate(struct dentry *opt_dentry, struct inode *inode) * Don't let the exec or mmap label be "*" or "@". */ skp = smk_fetch(XATTR_NAME_SMACKEXEC, inode, dp); - if (skp == &smack_known_star || skp == &smack_known_web) + if (IS_ERR(skp) || skp == &smack_known_star || + skp == &smack_known_web) skp = NULL; isp->smk_task = skp; + skp = smk_fetch(XATTR_NAME_SMACKMMAP, inode, dp); - if (skp == &smack_known_star || skp == &smack_known_web) + if (IS_ERR(skp) || skp == &smack_known_star || + skp == &smack_known_web) skp = NULL; isp->smk_mmap = skp; @@ -3302,8 +3315,8 @@ static int smack_setprocattr(struct task_struct *p, char *name, return -EINVAL; skp = smk_import_entry(value, size); - if (skp == NULL) - return -EINVAL; + if (IS_ERR(skp)) + return PTR_ERR(skp); /* * No process is ever allowed the web ("@") label. @@ -4078,8 +4091,10 @@ static int smack_audit_rule_init(u32 field, u32 op, char *rulestr, void **vrule) return -EINVAL; skp = smk_import_entry(rulestr, 0); - if (skp) - *rule = skp->smk_known; + if (IS_ERR(skp)) + return PTR_ERR(skp); + + *rule = skp->smk_known; return 0; } diff --git a/security/smack/smackfs.c b/security/smack/smackfs.c index 4aa12c8d3c63..3e4242617eea 100644 --- a/security/smack/smackfs.c +++ b/security/smack/smackfs.c @@ -338,8 +338,7 @@ static int smk_perm_from_str(const char *string) * @import: if non-zero, import labels * @len: label length limit * - * Returns 0 on success, -EINVAL on failure and -ENOENT when either subject - * or object is missing. + * Returns 0 on success, appropriate error code on failure. */ static int smk_fill_rule(const char *subject, const char *object, const char *access1, const char *access2, @@ -351,16 +350,16 @@ static int smk_fill_rule(const char *subject, const char *object, if (import) { rule->smk_subject = smk_import_entry(subject, len); - if (rule->smk_subject == NULL) - return -EINVAL; + if (IS_ERR(rule->smk_subject)) + return PTR_ERR(rule->smk_subject); rule->smk_object = smk_import_entry(object, len); - if (rule->smk_object == NULL) - return -EINVAL; + if (IS_ERR(rule->smk_object)) + return PTR_ERR(rule->smk_object); } else { cp = smk_parse_smack(subject, len); - if (cp == NULL) - return -EINVAL; + if (IS_ERR(cp)) + return PTR_ERR(cp); skp = smk_find_entry(cp); kfree(cp); if (skp == NULL) @@ -368,8 +367,8 @@ static int smk_fill_rule(const char *subject, const char *object, rule->smk_subject = skp; cp = smk_parse_smack(object, len); - if (cp == NULL) - return -EINVAL; + if (IS_ERR(cp)) + return PTR_ERR(cp); skp = smk_find_entry(cp); kfree(cp); if (skp == NULL) @@ -412,7 +411,7 @@ static int smk_parse_rule(const char *data, struct smack_parsed_rule *rule, * @import: if non-zero, import labels * @tokens: numer of substrings expected in data * - * Returns number of processed bytes on success, -1 on failure. + * Returns number of processed bytes on success, -ERRNO on failure. */ static ssize_t smk_parse_long_rule(char *data, struct smack_parsed_rule *rule, int import, int tokens) @@ -431,7 +430,7 @@ static ssize_t smk_parse_long_rule(char *data, struct smack_parsed_rule *rule, if (data[cnt] == '\0') /* Unexpected end of data */ - return -1; + return -EINVAL; tok[i] = data + cnt; @@ -529,14 +528,14 @@ static ssize_t smk_write_rules_list(struct file *file, const char __user *buf, while (cnt < count) { if (format == SMK_FIXED24_FMT) { rc = smk_parse_rule(data, &rule, 1); - if (rc != 0) { - rc = -EINVAL; + if (rc < 0) goto out; - } cnt = count; } else { rc = smk_parse_long_rule(data + cnt, &rule, 1, tokens); - if (rc <= 0) { + if (rc < 0) + goto out; + if (rc == 0) { rc = -EINVAL; goto out; } @@ -915,8 +914,10 @@ static ssize_t smk_set_cipso(struct file *file, const char __user *buf, mutex_lock(&smack_cipso_lock); skp = smk_import_entry(rule, 0); - if (skp == NULL) + if (IS_ERR(skp)) { + rc = PTR_ERR(skp); goto out; + } if (format == SMK_FIXED24_FMT) rule += SMK_LABELLEN; @@ -1237,8 +1238,8 @@ static ssize_t smk_write_netlbladdr(struct file *file, const char __user *buf, */ if (smack[0] != '-') { skp = smk_import_entry(smack, 0); - if (skp == NULL) { - rc = -EINVAL; + if (IS_ERR(skp)) { + rc = PTR_ERR(skp); goto free_out; } } else { @@ -1619,8 +1620,8 @@ static ssize_t smk_write_ambient(struct file *file, const char __user *buf, } skp = smk_import_entry(data, count); - if (skp == NULL) { - rc = -EINVAL; + if (IS_ERR(skp)) { + rc = PTR_ERR(skp); goto out; } @@ -1704,21 +1705,31 @@ static ssize_t smk_write_onlycap(struct file *file, const char __user *buf, if (data == NULL) return -ENOMEM; - /* - * Should the null string be passed in unset the onlycap value. - * This seems like something to be careful with as usually - * smk_import only expects to return NULL for errors. It - * is usually the case that a nullstring or "\n" would be - * bad to pass to smk_import but in fact this is useful here. - * - * smk_import will also reject a label beginning with '-', - * so "-usecapabilities" will also work. - */ - if (copy_from_user(data, buf, count) != 0) + if (copy_from_user(data, buf, count) != 0) { rc = -EFAULT; - else - smack_onlycap = smk_import_entry(data, count); + goto freeout; + } + /* + * Clear the smack_onlycap on invalid label errors. This means + * that we can pass a null string to unset the onlycap value. + * + * Importing will also reject a label beginning with '-', + * so "-usecapabilities" will also work. + * + * But do so only on invalid label, not on system errors. + */ + skp = smk_import_entry(data, count); + if (PTR_ERR(skp) == -EINVAL) + skp = NULL; + else if (IS_ERR(skp)) { + rc = PTR_ERR(skp); + goto freeout; + } + + smack_onlycap = skp; + +freeout: kfree(data); return rc; } @@ -1773,6 +1784,7 @@ static ssize_t smk_write_unconfined(struct file *file, const char __user *buf, size_t count, loff_t *ppos) { char *data; + struct smack_known *skp; int rc = count; if (!smack_privileged(CAP_MAC_ADMIN)) @@ -1782,21 +1794,31 @@ static ssize_t smk_write_unconfined(struct file *file, const char __user *buf, if (data == NULL) return -ENOMEM; - /* - * Should the null string be passed in unset the unconfined value. - * This seems like something to be careful with as usually - * smk_import only expects to return NULL for errors. It - * is usually the case that a nullstring or "\n" would be - * bad to pass to smk_import but in fact this is useful here. - * - * smk_import will also reject a label beginning with '-', - * so "-confine" will also work. - */ - if (copy_from_user(data, buf, count) != 0) + if (copy_from_user(data, buf, count) != 0) { rc = -EFAULT; - else - smack_unconfined = smk_import_entry(data, count); + goto freeout; + } + /* + * Clear the smack_unconfined on invalid label errors. This means + * that we can pass a null string to unset the unconfined value. + * + * Importing will also reject a label beginning with '-', + * so "-confine" will also work. + * + * But do so only on invalid label, not on system errors. + */ + skp = smk_import_entry(data, count); + if (PTR_ERR(skp) == -EINVAL) + skp = NULL; + else if (IS_ERR(skp)) { + rc = PTR_ERR(skp); + goto freeout; + } + + smack_unconfined = skp; + +freeout: kfree(data); return rc; } @@ -1980,7 +2002,7 @@ static ssize_t smk_user_access(struct file *file, const char __user *buf, res = smk_access(rule.smk_subject, rule.smk_object, rule.smk_access1, NULL); else if (res != -ENOENT) - return -EINVAL; + return res; /* * smk_access() can return a value > 0 in the "bringup" case. @@ -2209,8 +2231,8 @@ static ssize_t smk_write_revoke_subj(struct file *file, const char __user *buf, } cp = smk_parse_smack(data, count); - if (cp == NULL) { - rc = -EINVAL; + if (IS_ERR(cp)) { + rc = PTR_ERR(cp); goto free_out; } @@ -2341,10 +2363,10 @@ static ssize_t smk_write_syslog(struct file *file, const char __user *buf, rc = -EFAULT; else { skp = smk_import_entry(data, count); - if (skp == NULL) - rc = -EINVAL; + if (IS_ERR(skp)) + rc = PTR_ERR(skp); else - smack_syslog_label = smk_import_entry(data, count); + smack_syslog_label = skp; } kfree(data); From 01fa8474fba7e80f6a2ac31d0790385a993cb7ba Mon Sep 17 00:00:00 2001 From: Rafal Krypa Date: Thu, 21 May 2015 18:24:31 +0200 Subject: [PATCH 3/4] Smack: fix seq operations in smackfs Use proper RCU functions and read locking in smackfs seq_operations. Smack gets away with not using proper RCU functions in smackfs, because it never removes entries from these lists. But now one list will be needed (with interface in smackfs) that will have both elements added and removed to it. This change will also help any future changes implementing removal of unneeded entries from other Smack lists. The patch also fixes handling of pos argument in smk_seq_start and smk_seq_next. This fixes a bug in case when smackfs is read with a small buffer: Kernel panic - not syncing: Kernel mode fault at addr 0xfa0000011b CPU: 0 PID: 1292 Comm: dd Not tainted 4.1.0-rc1-00012-g98179b8 #13 Stack: 00000003 0000000d 7ff39e48 7f69fd00 7ff39ce0 601ae4b0 7ff39d50 600e587b 00000010 6039f690 7f69fd40 00612003 Call Trace: [<601ae4b0>] load2_seq_show+0x19/0x1d [<600e587b>] seq_read+0x168/0x331 [<600c5943>] __vfs_read+0x21/0x101 [<601a595e>] ? security_file_permission+0xf8/0x105 [<600c5ec6>] ? rw_verify_area+0x86/0xe2 [<600c5fc3>] vfs_read+0xa1/0x14c [<600c68e2>] SyS_read+0x57/0xa0 [<6001da60>] handle_syscall+0x60/0x80 [<6003087d>] userspace+0x442/0x548 [<6001aa77>] ? interrupt_end+0x0/0x80 [<6001daae>] ? copy_chunk_to_user+0x0/0x2b [<6002cb6b>] ? save_registers+0x1f/0x39 [<60032ef7>] ? arch_prctl+0xf5/0x170 [<6001a92d>] fork_handler+0x85/0x87 Signed-off-by: Rafal Krypa --- security/smack/smackfs.c | 50 +++++++++++++++++----------------------- 1 file changed, 21 insertions(+), 29 deletions(-) diff --git a/security/smack/smackfs.c b/security/smack/smackfs.c index 3e4242617eea..e40dc48737ff 100644 --- a/security/smack/smackfs.c +++ b/security/smack/smackfs.c @@ -566,23 +566,17 @@ static void *smk_seq_start(struct seq_file *s, loff_t *pos, struct list_head *head) { struct list_head *list; + int i = *pos; - /* - * This is 0 the first time through. - */ - if (s->index == 0) - s->private = head; + rcu_read_lock(); + for (list = rcu_dereference(list_next_rcu(head)); + list != head; + list = rcu_dereference(list_next_rcu(list))) { + if (i-- == 0) + return list; + } - if (s->private == NULL) - return NULL; - - list = s->private; - if (list_empty(list)) - return NULL; - - if (s->index == 0) - return list->next; - return list; + return NULL; } static void *smk_seq_next(struct seq_file *s, void *v, loff_t *pos, @@ -590,17 +584,15 @@ static void *smk_seq_next(struct seq_file *s, void *v, loff_t *pos, { struct list_head *list = v; - if (list_is_last(list, head)) { - s->private = NULL; - return NULL; - } - s->private = list->next; - return list->next; + ++*pos; + list = rcu_dereference(list_next_rcu(list)); + + return (list == head) ? NULL : list; } static void smk_seq_stop(struct seq_file *s, void *v) { - /* No-op */ + rcu_read_unlock(); } static void smk_rule_show(struct seq_file *s, struct smack_rule *srp, int max) @@ -660,7 +652,7 @@ static int load_seq_show(struct seq_file *s, void *v) { struct list_head *list = v; struct smack_master_list *smlp = - list_entry(list, struct smack_master_list, list); + list_entry_rcu(list, struct smack_master_list, list); smk_rule_show(s, smlp->smk_rule, SMK_LABELLEN); @@ -808,7 +800,7 @@ static int cipso_seq_show(struct seq_file *s, void *v) { struct list_head *list = v; struct smack_known *skp = - list_entry(list, struct smack_known, list); + list_entry_rcu(list, struct smack_known, list); struct netlbl_lsm_catmap *cmp = skp->smk_netlabel.attr.mls.cat; char sep = '/'; int i; @@ -999,7 +991,7 @@ static int cipso2_seq_show(struct seq_file *s, void *v) { struct list_head *list = v; struct smack_known *skp = - list_entry(list, struct smack_known, list); + list_entry_rcu(list, struct smack_known, list); struct netlbl_lsm_catmap *cmp = skp->smk_netlabel.attr.mls.cat; char sep = '/'; int i; @@ -1083,7 +1075,7 @@ static int netlbladdr_seq_show(struct seq_file *s, void *v) { struct list_head *list = v; struct smk_netlbladdr *skp = - list_entry(list, struct smk_netlbladdr, list); + list_entry_rcu(list, struct smk_netlbladdr, list); unsigned char *hp = (char *) &skp->smk_host.sin_addr.s_addr; int maskn; u32 temp_mask = be32_to_cpu(skp->smk_mask.s_addr); @@ -1917,7 +1909,7 @@ static int load_self_seq_show(struct seq_file *s, void *v) { struct list_head *list = v; struct smack_rule *srp = - list_entry(list, struct smack_rule, list); + list_entry_rcu(list, struct smack_rule, list); smk_rule_show(s, srp, SMK_LABELLEN); @@ -2046,7 +2038,7 @@ static int load2_seq_show(struct seq_file *s, void *v) { struct list_head *list = v; struct smack_master_list *smlp = - list_entry(list, struct smack_master_list, list); + list_entry_rcu(list, struct smack_master_list, list); smk_rule_show(s, smlp->smk_rule, SMK_LONGLABEL); @@ -2123,7 +2115,7 @@ static int load_self2_seq_show(struct seq_file *s, void *v) { struct list_head *list = v; struct smack_rule *srp = - list_entry(list, struct smack_rule, list); + list_entry_rcu(list, struct smack_rule, list); smk_rule_show(s, srp, SMK_LONGLABEL); From c0d77c884461fc0dec0411e49797dc3f3651c31b Mon Sep 17 00:00:00 2001 From: Rafal Krypa Date: Tue, 2 Jun 2015 11:23:48 +0200 Subject: [PATCH 4/4] Smack: allow multiple labels in onlycap Smack onlycap allows limiting of CAP_MAC_ADMIN and CAP_MAC_OVERRIDE to processes running with the configured label. But having single privileged label is not enough in some real use cases. On a complex system like Tizen, there maybe few programs that need to configure Smack policy in run-time and running them all with a single label is not always practical. This patch extends onlycap feature for multiple labels. They are configured in the same smackfs "onlycap" interface, separated by spaces. Signed-off-by: Rafal Krypa --- Documentation/security/Smack.txt | 6 +- security/smack/smack.h | 25 ++--- security/smack/smack_access.c | 41 ++++++++ security/smack/smackfs.c | 163 +++++++++++++++++++++---------- 4 files changed, 163 insertions(+), 72 deletions(-) diff --git a/Documentation/security/Smack.txt b/Documentation/security/Smack.txt index abc82f85215b..de5e1aeca7fb 100644 --- a/Documentation/security/Smack.txt +++ b/Documentation/security/Smack.txt @@ -206,11 +206,11 @@ netlabel label. The format accepted on write is: "%d.%d.%d.%d label" or "%d.%d.%d.%d/%d label". onlycap - This contains the label processes must have for CAP_MAC_ADMIN + This contains labels processes must have for CAP_MAC_ADMIN and CAP_MAC_OVERRIDE to be effective. If this file is empty these capabilities are effective at for processes with any - label. The value is set by writing the desired label to the - file or cleared by writing "-" to the file. + label. The values are set by writing the desired labels, separated + by spaces, to the file or cleared by writing "-" to the file. ptrace This is used to define the current ptrace policy 0 - default: this is the policy that relies on Smack access rules. diff --git a/security/smack/smack.h b/security/smack/smack.h index b8c1a869d85e..244e035e5a99 100644 --- a/security/smack/smack.h +++ b/security/smack/smack.h @@ -138,6 +138,11 @@ struct smk_port_label { struct smack_known *smk_out; /* outgoing label */ }; +struct smack_onlycap { + struct list_head list; + struct smack_known *smk_label; +}; + /* * Mount options */ @@ -249,6 +254,7 @@ int smk_netlbl_mls(int, char *, struct netlbl_lsm_secattr *, int); struct smack_known *smk_import_entry(const char *, int); void smk_insert_entry(struct smack_known *skp); struct smack_known *smk_find_entry(const char *); +int smack_privileged(int cap); /* * Shared data. @@ -257,7 +263,6 @@ extern int smack_enabled; extern int smack_cipso_direct; extern int smack_cipso_mapped; extern struct smack_known *smack_net_ambient; -extern struct smack_known *smack_onlycap; extern struct smack_known *smack_syslog_label; #ifdef CONFIG_SECURITY_SMACK_BRINGUP extern struct smack_known *smack_unconfined; @@ -276,6 +281,9 @@ extern struct mutex smack_known_lock; extern struct list_head smack_known_list; extern struct list_head smk_netlbladdr_list; +extern struct mutex smack_onlycap_lock; +extern struct list_head smack_onlycap_list; + #define SMACK_HASH_SLOTS 16 extern struct hlist_head smack_known_hash[SMACK_HASH_SLOTS]; @@ -331,21 +339,6 @@ static inline struct smack_known *smk_of_current(void) return smk_of_task(current_security()); } -/* - * Is the task privileged and allowed to be privileged - * by the onlycap rule. - */ -static inline int smack_privileged(int cap) -{ - struct smack_known *skp = smk_of_current(); - - if (!capable(cap)) - return 0; - if (smack_onlycap == NULL || smack_onlycap == skp) - return 1; - return 0; -} - /* * logging functions */ diff --git a/security/smack/smack_access.c b/security/smack/smack_access.c index 408e20be1ad7..00f6b38bffbd 100644 --- a/security/smack/smack_access.c +++ b/security/smack/smack_access.c @@ -617,3 +617,44 @@ struct smack_known *smack_from_secid(const u32 secid) rcu_read_unlock(); return &smack_known_invalid; } + +/* + * Unless a process is running with one of these labels + * even having CAP_MAC_OVERRIDE isn't enough to grant + * privilege to violate MAC policy. If no labels are + * designated (the empty list case) capabilities apply to + * everyone. + */ +LIST_HEAD(smack_onlycap_list); +DEFINE_MUTEX(smack_onlycap_lock); + +/* + * Is the task privileged and allowed to be privileged + * by the onlycap rule. + * + * Returns 1 if the task is allowed to be privileged, 0 if it's not. + */ +int smack_privileged(int cap) +{ + struct smack_known *skp = smk_of_current(); + struct smack_onlycap *sop; + + if (!capable(cap)) + return 0; + + rcu_read_lock(); + if (list_empty(&smack_onlycap_list)) { + rcu_read_unlock(); + return 1; + } + + list_for_each_entry_rcu(sop, &smack_onlycap_list, list) { + if (sop->smk_label == skp) { + rcu_read_unlock(); + return 1; + } + } + rcu_read_unlock(); + + return 0; +} diff --git a/security/smack/smackfs.c b/security/smack/smackfs.c index e40dc48737ff..f1c22a891b1a 100644 --- a/security/smack/smackfs.c +++ b/security/smack/smackfs.c @@ -87,16 +87,6 @@ int smack_cipso_direct = SMACK_CIPSO_DIRECT_DEFAULT; */ int smack_cipso_mapped = SMACK_CIPSO_MAPPED_DEFAULT; -/* - * Unless a process is running with this label even - * having CAP_MAC_OVERRIDE isn't enough to grant - * privilege to violate MAC policy. If no label is - * designated (the NULL case) capabilities apply to - * everyone. It is expected that the hat (^) label - * will be used if any label is used. - */ -struct smack_known *smack_onlycap; - #ifdef CONFIG_SECURITY_SMACK_BRINGUP /* * Allow one label to be unconfined. This is for @@ -1636,34 +1626,79 @@ static const struct file_operations smk_ambient_ops = { .llseek = default_llseek, }; -/** - * smk_read_onlycap - read() for smackfs/onlycap - * @filp: file pointer, not actually used - * @buf: where to put the result - * @cn: maximum to send along - * @ppos: where to start - * - * Returns number of bytes read or error code, as appropriate +/* + * Seq_file operations for /smack/onlycap */ -static ssize_t smk_read_onlycap(struct file *filp, char __user *buf, - size_t cn, loff_t *ppos) +static void *onlycap_seq_start(struct seq_file *s, loff_t *pos) { - char *smack = ""; - ssize_t rc = -EINVAL; - int asize; + return smk_seq_start(s, pos, &smack_onlycap_list); +} - if (*ppos != 0) - return 0; +static void *onlycap_seq_next(struct seq_file *s, void *v, loff_t *pos) +{ + return smk_seq_next(s, v, pos, &smack_onlycap_list); +} - if (smack_onlycap != NULL) - smack = smack_onlycap->smk_known; +static int onlycap_seq_show(struct seq_file *s, void *v) +{ + struct list_head *list = v; + struct smack_onlycap *sop = + list_entry_rcu(list, struct smack_onlycap, list); - asize = strlen(smack) + 1; + seq_puts(s, sop->smk_label->smk_known); + seq_putc(s, ' '); - if (cn >= asize) - rc = simple_read_from_buffer(buf, cn, ppos, smack, asize); + return 0; +} - return rc; +static const struct seq_operations onlycap_seq_ops = { + .start = onlycap_seq_start, + .next = onlycap_seq_next, + .show = onlycap_seq_show, + .stop = smk_seq_stop, +}; + +static int smk_open_onlycap(struct inode *inode, struct file *file) +{ + return seq_open(file, &onlycap_seq_ops); +} + +/** + * smk_list_swap_rcu - swap public list with a private one in RCU-safe way + * The caller must hold appropriate mutex to prevent concurrent modifications + * to the public list. + * Private list is assumed to be not accessible to other threads yet. + * + * @public: public list + * @private: private list + */ +static void smk_list_swap_rcu(struct list_head *public, + struct list_head *private) +{ + struct list_head *first, *last; + + if (list_empty(public)) { + list_splice_init_rcu(private, public, synchronize_rcu); + } else { + /* Remember public list before replacing it */ + first = public->next; + last = public->prev; + + /* Publish private list in place of public in RCU-safe way */ + private->prev->next = public; + private->next->prev = public; + rcu_assign_pointer(public->next, private->next); + public->prev = private->prev; + + synchronize_rcu(); + + /* When all readers are done with the old public list, + * attach it in place of private */ + private->next = first; + private->prev = last; + first->prev = private; + last->next = private; + } } /** @@ -1679,29 +1714,48 @@ static ssize_t smk_write_onlycap(struct file *file, const char __user *buf, size_t count, loff_t *ppos) { char *data; - struct smack_known *skp = smk_of_task(current->cred->security); + char *data_parse; + char *tok; + struct smack_known *skp; + struct smack_onlycap *sop; + struct smack_onlycap *sop2; + LIST_HEAD(list_tmp); int rc = count; if (!smack_privileged(CAP_MAC_ADMIN)) return -EPERM; - /* - * This can be done using smk_access() but is done - * explicitly for clarity. The smk_access() implementation - * would use smk_access(smack_onlycap, MAY_WRITE) - */ - if (smack_onlycap != NULL && smack_onlycap != skp) - return -EPERM; - data = kzalloc(count + 1, GFP_KERNEL); if (data == NULL) return -ENOMEM; if (copy_from_user(data, buf, count) != 0) { - rc = -EFAULT; - goto freeout; + kfree(data); + return -EFAULT; } + data_parse = data; + while ((tok = strsep(&data_parse, " ")) != NULL) { + if (!*tok) + continue; + + skp = smk_import_entry(tok, 0); + if (IS_ERR(skp)) { + rc = PTR_ERR(skp); + break; + } + + sop = kzalloc(sizeof(*sop), GFP_KERNEL); + if (sop == NULL) { + rc = -ENOMEM; + break; + } + + sop->smk_label = skp; + list_add_rcu(&sop->list, &list_tmp); + } + kfree(data); + /* * Clear the smack_onlycap on invalid label errors. This means * that we can pass a null string to unset the onlycap value. @@ -1710,26 +1764,29 @@ static ssize_t smk_write_onlycap(struct file *file, const char __user *buf, * so "-usecapabilities" will also work. * * But do so only on invalid label, not on system errors. + * The invalid label must be first to count as clearing attempt. */ - skp = smk_import_entry(data, count); - if (PTR_ERR(skp) == -EINVAL) - skp = NULL; - else if (IS_ERR(skp)) { - rc = PTR_ERR(skp); - goto freeout; + if (rc == -EINVAL && list_empty(&list_tmp)) + rc = count; + + if (rc >= 0) { + mutex_lock(&smack_onlycap_lock); + smk_list_swap_rcu(&smack_onlycap_list, &list_tmp); + mutex_unlock(&smack_onlycap_lock); } - smack_onlycap = skp; + list_for_each_entry_safe(sop, sop2, &list_tmp, list) + kfree(sop); -freeout: - kfree(data); return rc; } static const struct file_operations smk_onlycap_ops = { - .read = smk_read_onlycap, + .open = smk_open_onlycap, + .read = seq_read, .write = smk_write_onlycap, - .llseek = default_llseek, + .llseek = seq_lseek, + .release = seq_release, }; #ifdef CONFIG_SECURITY_SMACK_BRINGUP