From 2c3c1048746a4622d8c89a29670120dc8fab93c4 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 20:49:45 +0200 Subject: Adding upstream version 6.1.76. Signed-off-by: Daniel Baumann --- security/smack/smackfs.c | 3035 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 3035 insertions(+) create mode 100644 security/smack/smackfs.c (limited to 'security/smack/smackfs.c') diff --git a/security/smack/smackfs.c b/security/smack/smackfs.c new file mode 100644 index 000000000..da7db9e22 --- /dev/null +++ b/security/smack/smackfs.c @@ -0,0 +1,3035 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2007 Casey Schaufler + * + * Authors: + * Casey Schaufler + * Ahmed S. Darwish + * + * Special thanks to the authors of selinuxfs. + * + * Karl MacMillan + * James Morris + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "smack.h" + +#define BEBITS (sizeof(__be32) * 8) +/* + * smackfs pseudo filesystem. + */ + +enum smk_inos { + SMK_ROOT_INO = 2, + SMK_LOAD = 3, /* load policy */ + SMK_CIPSO = 4, /* load label -> CIPSO mapping */ + SMK_DOI = 5, /* CIPSO DOI */ + SMK_DIRECT = 6, /* CIPSO level indicating direct label */ + SMK_AMBIENT = 7, /* internet ambient label */ + SMK_NET4ADDR = 8, /* single label hosts */ + SMK_ONLYCAP = 9, /* the only "capable" label */ + SMK_LOGGING = 10, /* logging */ + SMK_LOAD_SELF = 11, /* task specific rules */ + SMK_ACCESSES = 12, /* access policy */ + SMK_MAPPED = 13, /* CIPSO level indicating mapped label */ + SMK_LOAD2 = 14, /* load policy with long labels */ + SMK_LOAD_SELF2 = 15, /* load task specific rules with long labels */ + 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) */ + SMK_SYSLOG = 20, /* change syslog label) */ + SMK_PTRACE = 21, /* set ptrace rule */ +#ifdef CONFIG_SECURITY_SMACK_BRINGUP + SMK_UNCONFINED = 22, /* define an unconfined label */ +#endif +#if IS_ENABLED(CONFIG_IPV6) + SMK_NET6ADDR = 23, /* single label IPv6 hosts */ +#endif /* CONFIG_IPV6 */ + SMK_RELABEL_SELF = 24, /* relabel possible without CAP_MAC_ADMIN */ +}; + +/* + * List locks + */ +static DEFINE_MUTEX(smack_cipso_lock); +static DEFINE_MUTEX(smack_ambient_lock); +static DEFINE_MUTEX(smk_net4addr_lock); +#if IS_ENABLED(CONFIG_IPV6) +static DEFINE_MUTEX(smk_net6addr_lock); +#endif /* CONFIG_IPV6 */ + +/* + * This is the "ambient" label for network traffic. + * If it isn't somehow marked, use this. + * It can be reset via smackfs/ambient + */ +struct smack_known *smack_net_ambient; + +/* + * This is the level in a CIPSO header that indicates a + * smack label is contained directly in the category set. + * It can be reset via smackfs/direct + */ +int smack_cipso_direct = SMACK_CIPSO_DIRECT_DEFAULT; + +/* + * This is the level in a CIPSO header that indicates a + * secid is contained directly in the category set. + * It can be reset via smackfs/mapped + */ +int smack_cipso_mapped = SMACK_CIPSO_MAPPED_DEFAULT; + +#ifdef CONFIG_SECURITY_SMACK_BRINGUP +/* + * Allow one label to be unconfined. This is for + * debugging and application bring-up purposes only. + * It is bad and wrong, but everyone seems to expect + * to have it. + */ +struct smack_known *smack_unconfined; +#endif + +/* + * If this value is set restrict syslog use to the label specified. + * It can be reset via smackfs/syslog + */ +struct smack_known *smack_syslog_label; + +/* + * Ptrace current rule + * SMACK_PTRACE_DEFAULT regular smack ptrace rules (/proc based) + * SMACK_PTRACE_EXACT labels must match, but can be overriden with + * CAP_SYS_PTRACE + * SMACK_PTRACE_DRACONIAN lables must match, CAP_SYS_PTRACE has no effect + */ +int smack_ptrace_rule = SMACK_PTRACE_DEFAULT; + +/* + * Certain IP addresses may be designated as single label hosts. + * Packets are sent there unlabeled, but only from tasks that + * can write to the specified label. + */ + +LIST_HEAD(smk_net4addr_list); +#if IS_ENABLED(CONFIG_IPV6) +LIST_HEAD(smk_net6addr_list); +#endif /* CONFIG_IPV6 */ + +/* + * Rule lists are maintained for each label. + */ +struct smack_parsed_rule { + struct smack_known *smk_subject; + struct smack_known *smk_object; + int smk_access1; + int smk_access2; +}; + +static int smk_cipso_doi_value = SMACK_CIPSO_DOI_DEFAULT; + +/* + * Values for parsing cipso rules + * SMK_DIGITLEN: Length of a digit field in a rule. + * SMK_CIPSOMIN: Minimum possible cipso rule length. + * SMK_CIPSOMAX: Maximum possible cipso rule length. + */ +#define SMK_DIGITLEN 4 +#define SMK_CIPSOMIN (SMK_LABELLEN + 2 * SMK_DIGITLEN) +#define SMK_CIPSOMAX (SMK_CIPSOMIN + SMACK_CIPSO_MAXCATNUM * SMK_DIGITLEN) + +/* + * Values for parsing MAC rules + * SMK_ACCESS: Maximum possible combination of access permissions + * SMK_ACCESSLEN: Maximum length for a rule access field + * SMK_LOADLEN: Smack rule length + */ +#define SMK_OACCESS "rwxa" +#define SMK_ACCESS "rwxatl" +#define SMK_OACCESSLEN (sizeof(SMK_OACCESS) - 1) +#define SMK_ACCESSLEN (sizeof(SMK_ACCESS) - 1) +#define SMK_OLOADLEN (SMK_LABELLEN + SMK_LABELLEN + SMK_OACCESSLEN) +#define SMK_LOADLEN (SMK_LABELLEN + SMK_LABELLEN + SMK_ACCESSLEN) + +/* + * Stricly for CIPSO level manipulation. + * Set the category bit number in a smack label sized buffer. + */ +static inline void smack_catset_bit(unsigned int cat, char *catsetp) +{ + if (cat == 0 || cat > (SMK_CIPSOLEN * 8)) + return; + + catsetp[(cat - 1) / 8] |= 0x80 >> ((cat - 1) % 8); +} + +/** + * smk_netlabel_audit_set - fill a netlbl_audit struct + * @nap: structure to fill + */ +static void smk_netlabel_audit_set(struct netlbl_audit *nap) +{ + struct smack_known *skp = smk_of_current(); + + nap->loginuid = audit_get_loginuid(current); + nap->sessionid = audit_get_sessionid(current); + nap->secid = skp->smk_secid; +} + +/* + * Value for parsing single label host rules + * "1.2.3.4 X" + */ +#define SMK_NETLBLADDRMIN 9 + +/** + * 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 + * + * 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 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_parsed_rule *srp, + struct list_head *rule_list, + struct mutex *rule_lock) +{ + struct smack_rule *sp; + int found = 0; + int rc = 0; + + mutex_lock(rule_lock); + + /* + * Because the object label is less likely to match + * than the subject label check it first + */ + list_for_each_entry_rcu(sp, rule_list, list) { + if (sp->smk_object == srp->smk_object && + sp->smk_subject == srp->smk_subject) { + found = 1; + sp->smk_access |= srp->smk_access1; + sp->smk_access &= ~srp->smk_access2; + break; + } + } + + if (found == 0) { + sp = kmem_cache_zalloc(smack_rule_cache, 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); + } + +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; + + 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; + case 'l': + case 'L': + perm |= MAY_LOCK; + break; + case 'b': + case 'B': + perm |= MAY_BRINGUP; + break; + default: + return perm; + } +} + +/** + * smk_fill_rule - Fill Smack rule from strings + * @subject: subject label string + * @object: object label string + * @access1: access string + * @access2: string with permissions to be removed + * @rule: Smack rule + * @import: if non-zero, import labels + * @len: label length limit + * + * 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, + struct smack_parsed_rule *rule, int import, + int len) +{ + const char *cp; + struct smack_known *skp; + + if (import) { + rule->smk_subject = smk_import_entry(subject, len); + if (IS_ERR(rule->smk_subject)) + return PTR_ERR(rule->smk_subject); + + rule->smk_object = smk_import_entry(object, len); + if (IS_ERR(rule->smk_object)) + return PTR_ERR(rule->smk_object); + } else { + cp = smk_parse_smack(subject, len); + if (IS_ERR(cp)) + return PTR_ERR(cp); + skp = smk_find_entry(cp); + kfree(cp); + if (skp == NULL) + return -ENOENT; + rule->smk_subject = skp; + + cp = smk_parse_smack(object, len); + if (IS_ERR(cp)) + return PTR_ERR(cp); + skp = smk_find_entry(cp); + kfree(cp); + if (skp == NULL) + return -ENOENT; + rule->smk_object = skp; + } + + 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; +} + +/** + * smk_parse_rule - parse Smack rule from load string + * @data: string to be parsed whose size is SMK_LOADLEN + * @rule: Smack rule + * @import: if non-zero, import labels + * + * Returns 0 on success, -1 on errors. + */ +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, 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: Will be filled with Smack parsed rule + * @import: if non-zero, import labels + * @tokens: number of substrings expected in data + * + * 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) +{ + ssize_t cnt = 0; + char *tok[4]; + int rc; + int i; + + /* + * Parsing the rule in-place, filling all white-spaces with '\0' + */ + for (i = 0; i < tokens; ++i) { + while (isspace(data[cnt])) + data[cnt++] = '\0'; + + if (data[cnt] == '\0') + /* Unexpected end of data */ + return -EINVAL; + + tok[i] = data + cnt; + + while (data[cnt] && !isspace(data[cnt])) + ++cnt; + } + while (isspace(data[cnt])) + data[cnt++] = '\0'; + + while (i < 4) + tok[i++] = NULL; + + rc = smk_fill_rule(tok[0], tok[1], tok[2], tok[3], rule, import, 0); + return rc == 0 ? cnt : rc; +} + +#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 + * @buf: where to get the data from + * @count: bytes sent + * @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 or /smack/change-rule format. + * + * Get one smack access rule from above. + * The format for SMK_LONG_FMT is: + * "subjectobjectaccess[...]" + * The format for SMK_FIXED24_FMT is exactly: + * "subject object rwxat" + * The format for SMK_CHANGE_FMT is: + * "subjectobject + * acc_enableacc_disable[...]" + */ +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_parsed_rule rule; + char *data; + int rc; + int trunc = 0; + int tokens; + ssize_t cnt = 0; + + /* + * No partial writes. + * Enough data must be present. + */ + if (*ppos != 0) + return -EINVAL; + + if (format == SMK_FIXED24_FMT) { + /* + * Minor hack for backward compatibility + */ + if (count < SMK_OLOADLEN || count > SMK_LOADLEN) + return -EINVAL; + } else { + if (count >= PAGE_SIZE) { + count = PAGE_SIZE - 1; + trunc = 1; + } + } + + data = memdup_user_nul(buf, count); + if (IS_ERR(data)) + return PTR_ERR(data); + + /* + * In case of parsing only part of user buf, + * avoid having partial rule at the data buffer + */ + if (trunc) { + while (count > 0 && (data[count - 1] != '\n')) + --count; + if (count == 0) { + rc = -EINVAL; + goto out; + } + } + + data[count] = '\0'; + tokens = (format == SMK_CHANGE_FMT ? 4 : 3); + while (cnt < count) { + if (format == SMK_FIXED24_FMT) { + rc = smk_parse_rule(data, &rule, 1); + if (rc < 0) + goto out; + cnt = count; + } else { + rc = smk_parse_long_rule(data + cnt, &rule, 1, tokens); + if (rc < 0) + goto out; + if (rc == 0) { + rc = -EINVAL; + goto out; + } + cnt += rc; + } + + if (rule_list == NULL) + rc = smk_set_access(&rule, &rule.smk_subject->smk_rules, + &rule.smk_subject->smk_rules_lock); + else + rc = smk_set_access(&rule, rule_list, rule_lock); + + if (rc) + goto out; + } + + rc = cnt; +out: + kfree(data); + return rc; +} + +/* + * Core logic for smackfs seq list operations. + */ + +static void *smk_seq_start(struct seq_file *s, loff_t *pos, + struct list_head *head) +{ + struct list_head *list; + int i = *pos; + + 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; + } + + return NULL; +} + +static void *smk_seq_next(struct seq_file *s, void *v, loff_t *pos, + struct list_head *head) +{ + struct list_head *list = v; + + ++*pos; + list = rcu_dereference(list_next_rcu(list)); + + return (list == head) ? NULL : list; +} + +static void smk_seq_stop(struct seq_file *s, void *v) +{ + rcu_read_unlock(); +} + +static void smk_rule_show(struct seq_file *s, struct smack_rule *srp, int max) +{ + /* + * Don't show any rules with label names too long for + * interface file (/smack/load or /smack/load2) + * because you should expect to be able to write + * anything you read back. + */ + if (strlen(srp->smk_subject->smk_known) >= max || + strlen(srp->smk_object->smk_known) >= max) + return; + + if (srp->smk_access == 0) + return; + + seq_printf(s, "%s %s", + srp->smk_subject->smk_known, + srp->smk_object->smk_known); + + seq_putc(s, ' '); + + if (srp->smk_access & MAY_READ) + seq_putc(s, 'r'); + if (srp->smk_access & MAY_WRITE) + seq_putc(s, 'w'); + if (srp->smk_access & MAY_EXEC) + seq_putc(s, 'x'); + if (srp->smk_access & MAY_APPEND) + seq_putc(s, 'a'); + if (srp->smk_access & MAY_TRANSMUTE) + seq_putc(s, 't'); + if (srp->smk_access & MAY_LOCK) + seq_putc(s, 'l'); + if (srp->smk_access & MAY_BRINGUP) + seq_putc(s, 'b'); + + seq_putc(s, '\n'); +} + +/* + * Seq_file read operations for /smack/load + */ + +static void *load2_seq_start(struct seq_file *s, loff_t *pos) +{ + return smk_seq_start(s, pos, &smack_known_list); +} + +static void *load2_seq_next(struct seq_file *s, void *v, loff_t *pos) +{ + return smk_seq_next(s, v, pos, &smack_known_list); +} + +static int load_seq_show(struct seq_file *s, void *v) +{ + struct list_head *list = v; + struct smack_rule *srp; + struct smack_known *skp = + list_entry_rcu(list, struct smack_known, list); + + list_for_each_entry_rcu(srp, &skp->smk_rules, list) + smk_rule_show(s, srp, SMK_LABELLEN); + + return 0; +} + +static const struct seq_operations load_seq_ops = { + .start = load2_seq_start, + .next = load2_seq_next, + .show = load_seq_show, + .stop = smk_seq_stop, +}; + +/** + * smk_open_load - open() for /smack/load + * @inode: inode structure representing file + * @file: "load" file pointer + * + * For reading, use load_seq_* seq_file reading operations. + */ +static int smk_open_load(struct inode *inode, struct file *file) +{ + return seq_open(file, &load_seq_ops); +} + +/** + * smk_write_load - write() for /smack/load + * @file: file pointer, not actually used + * @buf: where to get the data from + * @count: bytes sent + * @ppos: where to start - must be 0 + * + */ +static ssize_t smk_write_load(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + /* + * Must have privilege. + * No partial writes. + * Enough data must be present. + */ + if (!smack_privileged(CAP_MAC_ADMIN)) + return -EPERM; + + return smk_write_rules_list(file, buf, count, ppos, NULL, NULL, + SMK_FIXED24_FMT); +} + +static const struct file_operations smk_load_ops = { + .open = smk_open_load, + .read = seq_read, + .llseek = seq_lseek, + .write = smk_write_load, + .release = seq_release, +}; + +/** + * smk_cipso_doi - initialize the CIPSO domain + */ +static void smk_cipso_doi(void) +{ + int rc; + struct cipso_v4_doi *doip; + struct netlbl_audit nai; + + smk_netlabel_audit_set(&nai); + + rc = netlbl_cfg_map_del(NULL, PF_INET, NULL, NULL, &nai); + if (rc != 0) + printk(KERN_WARNING "%s:%d remove rc = %d\n", + __func__, __LINE__, rc); + + doip = kmalloc(sizeof(struct cipso_v4_doi), GFP_KERNEL | __GFP_NOFAIL); + doip->map.std = NULL; + doip->doi = smk_cipso_doi_value; + doip->type = CIPSO_V4_MAP_PASS; + doip->tags[0] = CIPSO_V4_TAG_RBITMAP; + for (rc = 1; rc < CIPSO_V4_TAG_MAXCNT; rc++) + doip->tags[rc] = CIPSO_V4_TAG_INVALID; + + rc = netlbl_cfg_cipsov4_add(doip, &nai); + if (rc != 0) { + printk(KERN_WARNING "%s:%d cipso add rc = %d\n", + __func__, __LINE__, rc); + kfree(doip); + return; + } + rc = netlbl_cfg_cipsov4_map_add(doip->doi, NULL, NULL, NULL, &nai); + if (rc != 0) { + printk(KERN_WARNING "%s:%d map add rc = %d\n", + __func__, __LINE__, rc); + netlbl_cfg_cipsov4_del(doip->doi, &nai); + return; + } +} + +/** + * smk_unlbl_ambient - initialize the unlabeled domain + * @oldambient: previous domain string + */ +static void smk_unlbl_ambient(char *oldambient) +{ + int rc; + struct netlbl_audit nai; + + smk_netlabel_audit_set(&nai); + + if (oldambient != NULL) { + rc = netlbl_cfg_map_del(oldambient, PF_INET, NULL, NULL, &nai); + if (rc != 0) + printk(KERN_WARNING "%s:%d remove rc = %d\n", + __func__, __LINE__, rc); + } + if (smack_net_ambient == NULL) + smack_net_ambient = &smack_known_floor; + + rc = netlbl_cfg_unlbl_map_add(smack_net_ambient->smk_known, PF_INET, + NULL, NULL, &nai); + if (rc != 0) + printk(KERN_WARNING "%s:%d add rc = %d\n", + __func__, __LINE__, rc); +} + +/* + * Seq_file read operations for /smack/cipso + */ + +static void *cipso_seq_start(struct seq_file *s, loff_t *pos) +{ + return smk_seq_start(s, pos, &smack_known_list); +} + +static void *cipso_seq_next(struct seq_file *s, void *v, loff_t *pos) +{ + return smk_seq_next(s, v, pos, &smack_known_list); +} + +/* + * Print cipso labels in format: + * label level[/cat[,cat]] + */ +static int cipso_seq_show(struct seq_file *s, void *v) +{ + struct list_head *list = v; + struct smack_known *skp = + list_entry_rcu(list, struct smack_known, list); + struct netlbl_lsm_catmap *cmp = skp->smk_netlabel.attr.mls.cat; + char sep = '/'; + int i; + + /* + * Don't show a label that could not have been set using + * /smack/cipso. This is in support of the notion that + * anything read from /smack/cipso ought to be writeable + * to /smack/cipso. + * + * /smack/cipso2 should be used instead. + */ + if (strlen(skp->smk_known) >= SMK_LABELLEN) + return 0; + + seq_printf(s, "%s %3d", skp->smk_known, skp->smk_netlabel.attr.mls.lvl); + + for (i = netlbl_catmap_walk(cmp, 0); i >= 0; + i = netlbl_catmap_walk(cmp, i + 1)) { + seq_printf(s, "%c%d", sep, i); + sep = ','; + } + + seq_putc(s, '\n'); + + return 0; +} + +static const struct seq_operations cipso_seq_ops = { + .start = cipso_seq_start, + .next = cipso_seq_next, + .show = cipso_seq_show, + .stop = smk_seq_stop, +}; + +/** + * smk_open_cipso - open() for /smack/cipso + * @inode: inode structure representing file + * @file: "cipso" file pointer + * + * Connect our cipso_seq_* operations with /smack/cipso + * file_operations + */ +static int smk_open_cipso(struct inode *inode, struct file *file) +{ + return seq_open(file, &cipso_seq_ops); +} + +/** + * smk_set_cipso - do the work for write() for cipso and cipso2 + * @file: file pointer, not actually used + * @buf: where to get the data from + * @count: bytes sent + * @ppos: where to start + * @format: /smack/cipso or /smack/cipso2 + * + * Accepts only one cipso rule per write call. + * Returns number of bytes written or error code, as appropriate + */ +static ssize_t smk_set_cipso(struct file *file, const char __user *buf, + size_t count, loff_t *ppos, int format) +{ + struct netlbl_lsm_catmap *old_cat; + struct smack_known *skp; + struct netlbl_lsm_secattr ncats; + char mapcatset[SMK_CIPSOLEN]; + int maplevel; + unsigned int cat; + int catlen; + ssize_t rc = -EINVAL; + char *data = NULL; + char *rule; + int ret; + int i; + + /* + * Must have privilege. + * No partial writes. + * Enough data must be present. + */ + if (!smack_privileged(CAP_MAC_ADMIN)) + return -EPERM; + if (*ppos != 0) + return -EINVAL; + if (format == SMK_FIXED24_FMT && + (count < SMK_CIPSOMIN || count > SMK_CIPSOMAX)) + return -EINVAL; + if (count > PAGE_SIZE) + return -EINVAL; + + data = memdup_user_nul(buf, count); + if (IS_ERR(data)) + return PTR_ERR(data); + + rule = data; + /* + * Only allow one writer at a time. Writes should be + * quite rare and small in any case. + */ + mutex_lock(&smack_cipso_lock); + + skp = smk_import_entry(rule, 0); + if (IS_ERR(skp)) { + rc = PTR_ERR(skp); + goto out; + } + + if (format == SMK_FIXED24_FMT) + rule += SMK_LABELLEN; + else + rule += strlen(skp->smk_known) + 1; + + if (rule > data + count) { + rc = -EOVERFLOW; + goto out; + } + + ret = sscanf(rule, "%d", &maplevel); + if (ret != 1 || maplevel < 0 || maplevel > SMACK_CIPSO_MAXLEVEL) + goto out; + + rule += SMK_DIGITLEN; + if (rule > data + count) { + rc = -EOVERFLOW; + goto out; + } + + ret = sscanf(rule, "%d", &catlen); + if (ret != 1 || catlen < 0 || catlen > SMACK_CIPSO_MAXCATNUM) + goto out; + + if (format == SMK_FIXED24_FMT && + count != (SMK_CIPSOMIN + catlen * SMK_DIGITLEN)) + goto out; + + memset(mapcatset, 0, sizeof(mapcatset)); + + for (i = 0; i < catlen; i++) { + rule += SMK_DIGITLEN; + if (rule > data + count) { + rc = -EOVERFLOW; + goto out; + } + ret = sscanf(rule, "%u", &cat); + if (ret != 1 || cat > SMACK_CIPSO_MAXCATNUM) + goto out; + + smack_catset_bit(cat, mapcatset); + } + + rc = smk_netlbl_mls(maplevel, mapcatset, &ncats, SMK_CIPSOLEN); + if (rc >= 0) { + old_cat = skp->smk_netlabel.attr.mls.cat; + skp->smk_netlabel.attr.mls.cat = ncats.attr.mls.cat; + skp->smk_netlabel.attr.mls.lvl = ncats.attr.mls.lvl; + synchronize_rcu(); + netlbl_catmap_free(old_cat); + rc = count; + /* + * This mapping may have been cached, so clear the cache. + */ + netlbl_cache_invalidate(); + } + +out: + mutex_unlock(&smack_cipso_lock); + kfree(data); + return rc; +} + +/** + * smk_write_cipso - write() for /smack/cipso + * @file: file pointer, not actually used + * @buf: where to get the data from + * @count: bytes sent + * @ppos: where to start + * + * Accepts only one cipso rule per write call. + * Returns number of bytes written or error code, as appropriate + */ +static ssize_t smk_write_cipso(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + return smk_set_cipso(file, buf, count, ppos, SMK_FIXED24_FMT); +} + +static const struct file_operations smk_cipso_ops = { + .open = smk_open_cipso, + .read = seq_read, + .llseek = seq_lseek, + .write = smk_write_cipso, + .release = seq_release, +}; + +/* + * Seq_file read operations for /smack/cipso2 + */ + +/* + * Print cipso labels in format: + * label level[/cat[,cat]] + */ +static int cipso2_seq_show(struct seq_file *s, void *v) +{ + struct list_head *list = v; + struct smack_known *skp = + list_entry_rcu(list, struct smack_known, list); + struct netlbl_lsm_catmap *cmp = skp->smk_netlabel.attr.mls.cat; + char sep = '/'; + int i; + + seq_printf(s, "%s %3d", skp->smk_known, skp->smk_netlabel.attr.mls.lvl); + + for (i = netlbl_catmap_walk(cmp, 0); i >= 0; + i = netlbl_catmap_walk(cmp, i + 1)) { + seq_printf(s, "%c%d", sep, i); + sep = ','; + } + + seq_putc(s, '\n'); + + return 0; +} + +static const struct seq_operations cipso2_seq_ops = { + .start = cipso_seq_start, + .next = cipso_seq_next, + .show = cipso2_seq_show, + .stop = smk_seq_stop, +}; + +/** + * smk_open_cipso2 - open() for /smack/cipso2 + * @inode: inode structure representing file + * @file: "cipso2" file pointer + * + * Connect our cipso_seq_* operations with /smack/cipso2 + * file_operations + */ +static int smk_open_cipso2(struct inode *inode, struct file *file) +{ + return seq_open(file, &cipso2_seq_ops); +} + +/** + * smk_write_cipso2 - write() for /smack/cipso2 + * @file: file pointer, not actually used + * @buf: where to get the data from + * @count: bytes sent + * @ppos: where to start + * + * Accepts only one cipso rule per write call. + * Returns number of bytes written or error code, as appropriate + */ +static ssize_t smk_write_cipso2(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + return smk_set_cipso(file, buf, count, ppos, SMK_LONG_FMT); +} + +static const struct file_operations smk_cipso2_ops = { + .open = smk_open_cipso2, + .read = seq_read, + .llseek = seq_lseek, + .write = smk_write_cipso2, + .release = seq_release, +}; + +/* + * Seq_file read operations for /smack/netlabel + */ + +static void *net4addr_seq_start(struct seq_file *s, loff_t *pos) +{ + return smk_seq_start(s, pos, &smk_net4addr_list); +} + +static void *net4addr_seq_next(struct seq_file *s, void *v, loff_t *pos) +{ + return smk_seq_next(s, v, pos, &smk_net4addr_list); +} + +/* + * Print host/label pairs + */ +static int net4addr_seq_show(struct seq_file *s, void *v) +{ + struct list_head *list = v; + struct smk_net4addr *skp = + list_entry_rcu(list, struct smk_net4addr, list); + char *kp = SMACK_CIPSO_OPTION; + + if (skp->smk_label != NULL) + kp = skp->smk_label->smk_known; + seq_printf(s, "%pI4/%d %s\n", &skp->smk_host.s_addr, + skp->smk_masks, kp); + + return 0; +} + +static const struct seq_operations net4addr_seq_ops = { + .start = net4addr_seq_start, + .next = net4addr_seq_next, + .show = net4addr_seq_show, + .stop = smk_seq_stop, +}; + +/** + * smk_open_net4addr - open() for /smack/netlabel + * @inode: inode structure representing file + * @file: "netlabel" file pointer + * + * Connect our net4addr_seq_* operations with /smack/netlabel + * file_operations + */ +static int smk_open_net4addr(struct inode *inode, struct file *file) +{ + return seq_open(file, &net4addr_seq_ops); +} + +/** + * smk_net4addr_insert + * @new : netlabel to insert + * + * This helper insert netlabel in the smack_net4addrs list + * sorted by netmask length (longest to smallest) + * locked by &smk_net4addr_lock in smk_write_net4addr + * + */ +static void smk_net4addr_insert(struct smk_net4addr *new) +{ + struct smk_net4addr *m; + struct smk_net4addr *m_next; + + if (list_empty(&smk_net4addr_list)) { + list_add_rcu(&new->list, &smk_net4addr_list); + return; + } + + m = list_entry_rcu(smk_net4addr_list.next, + struct smk_net4addr, list); + + /* the comparison '>' is a bit hacky, but works */ + if (new->smk_masks > m->smk_masks) { + list_add_rcu(&new->list, &smk_net4addr_list); + return; + } + + list_for_each_entry_rcu(m, &smk_net4addr_list, list) { + if (list_is_last(&m->list, &smk_net4addr_list)) { + list_add_rcu(&new->list, &m->list); + return; + } + m_next = list_entry_rcu(m->list.next, + struct smk_net4addr, list); + if (new->smk_masks > m_next->smk_masks) { + list_add_rcu(&new->list, &m->list); + return; + } + } +} + + +/** + * smk_write_net4addr - write() for /smack/netlabel + * @file: file pointer, not actually used + * @buf: where to get the data from + * @count: bytes sent + * @ppos: where to start + * + * Accepts only one net4addr per write call. + * Returns number of bytes written or error code, as appropriate + */ +static ssize_t smk_write_net4addr(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + struct smk_net4addr *snp; + struct sockaddr_in newname; + char *smack; + struct smack_known *skp = NULL; + char *data; + char *host = (char *)&newname.sin_addr.s_addr; + int rc; + struct netlbl_audit audit_info; + struct in_addr mask; + unsigned int m; + unsigned int masks; + int found; + u32 mask_bits = (1<<31); + __be32 nsa; + u32 temp_mask; + + /* + * Must have privilege. + * No partial writes. + * Enough data must be present. + * "