/* * Copyright (C) 2007 Casey Schaufler * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2. * * 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 "smack.h" /* * 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_NETLBLADDR = 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 */ }; /* * 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); /* * This is the "ambient" label for network traffic. * If it isn't somehow marked, use this. * It can be reset via smackfs/ambient */ char *smack_net_ambient = smack_known_floor.smk_known; /* * 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; /* * 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. */ char *smack_onlycap; /* * 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_netlbladdr_list); /* * Rule lists are maintained for each label. * This master list is just for reading /smack/load. */ struct smack_master_list { struct list_head list; struct smack_rule *smk_rule; }; LIST_HEAD(smack_rule_list); static int smk_cipso_doi_value = SMACK_CIPSO_DOI_DEFAULT; const char *smack_cipso_option = SMACK_CIPSO_OPTION; /* * 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 "rwxat" #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) /** * smk_netlabel_audit_set - fill a netlbl_audit struct * @nap: structure to fill */ static void smk_netlabel_audit_set(struct netlbl_audit *nap) { nap->loginuid = audit_get_loginuid(current); nap->sessionid = audit_get_sessionid(current); nap->secid = smack_to_secid(smk_of_current()); } /* * Values for parsing single label host rules * "1.2.3.4 X" * "192.168.138.129/32 abcdefghijklmnopqrstuvw" */ #define SMK_NETLBLADDRMIN 9 #define SMK_NETLBLADDRMAX 42 /** * smk_set_access - add a rule to the rule list * @srp: the new rule to add * @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 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) { struct smack_rule *sp; int found = 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_access; break; } } if (found == 0) list_add_rcu(&srp->list, rule_list); mutex_unlock(rule_lock); return found; } /** * 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 */ static int smk_parse_rule(const char *data, struct smack_rule *rule, int import) { char smack[SMK_LABELLEN]; struct smack_known *skp; if (import) { rule->smk_subject = smk_import(data, 0); if (rule->smk_subject == NULL) return -1; rule->smk_object = smk_import(data + SMK_LABELLEN, 0); if (rule->smk_object == NULL) return -1; } else { smk_parse_smack(data, 0, smack); skp = smk_find_entry(smack); if (skp == NULL) return -1; rule->smk_subject = skp->smk_known; smk_parse_smack(data + SMK_LABELLEN, 0, smack); skp = smk_find_entry(smack); if (skp == NULL) return -1; rule->smk_object = skp->smk_known; } rule->smk_access = 0; switch (data[SMK_LABELLEN + SMK_LABELLEN]) { case '-': break; case 'r': case 'R': rule->smk_access |= MAY_READ; break; default: return -1; } switch (data[SMK_LABELLEN + SMK_LABELLEN + 1]) { case '-': break; case 'w': case 'W': rule->smk_access |= MAY_WRITE; break; default: return -1; } switch (data[SMK_LABELLEN + SMK_LABELLEN + 2]) { case '-': break; case 'x': case 'X': rule->smk_access |= MAY_EXEC; break; default: return -1; } switch (data[SMK_LABELLEN + SMK_LABELLEN + 3]) { case '-': break; case 'a': case 'A': rule->smk_access |= MAY_APPEND; break; default: return -1; } switch (data[SMK_LABELLEN + SMK_LABELLEN + 4]) { case '-': break; case 't': case 'T': rule->smk_access |= MAY_TRANSMUTE; break; default: return -1; } return 0; } /** * smk_write_load_list - write() for any /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 * @rule_list: the list of rules to write to * @rule_lock: lock for the rule list * * Get one smack access rule from above. * The format is exactly: * char subject[SMK_LABELLEN] * char object[SMK_LABELLEN] * char access[SMK_ACCESSLEN] * * writes must be SMK_LABELLEN+SMK_LABELLEN+SMK_ACCESSLEN bytes. */ static ssize_t smk_write_load_list(struct file *file, const char __user *buf, size_t count, loff_t *ppos, struct list_head *rule_list, struct mutex *rule_lock) { struct smack_master_list *smlp; struct smack_known *skp; struct smack_rule *rule; char *data; int rc = -EINVAL; int load = 0; /* * No partial writes. * Enough data must be present. */ if (*ppos != 0) return -EINVAL; /* * Minor hack for backward compatibility */ if (count < (SMK_OLOADLEN) || count > SMK_LOADLEN) return -EINVAL; data = kzalloc(SMK_LOADLEN, GFP_KERNEL); if (data == NULL) return -ENOMEM; if (copy_from_user(data, buf, count) != 0) { rc = -EFAULT; goto out; } /* * More on the minor hack for backward compatibility */ if (count == (SMK_OLOADLEN)) data[SMK_OLOADLEN] = '-'; rule = kzalloc(sizeof(*rule), GFP_KERNEL); if (rule == NULL) { rc = -ENOMEM; goto out; } if (smk_parse_rule(data, rule, 1)) goto out_free_rule; if (rule_list == NULL) { load = 1; skp = smk_find_entry(rule->smk_subject); rule_list = &skp->smk_rules; rule_lock = &skp->smk_rules_lock; } rc = count; /* * If this is "load" as opposed to "load-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 (load && !smk_set_access(rule, rule_list, rule_lock)) { smlp = kzalloc(sizeof(*smlp), GFP_KERNEL); if (smlp != NULL) { smlp->smk_rule = rule; list_add_rcu(&smlp->list, &smack_rule_list); } else rc = -ENOMEM; goto out; } out_free_rule: kfree(rule); 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; /* * This is 0 the first time through. */ if (s->index == 0) s->private = head; if (s->private == NULL) return NULL; list = s->private; if (list_empty(list)) return NULL; if (s->index == 0) return list->next; return list; } static void *smk_seq_next(struct seq_file *s, void *v, loff_t *pos, struct list_head *head) { struct list_head *list = v; if (list_is_last(list, head)) { s->private = NULL; return NULL; } s->private = list->next; return list->next; } static void smk_seq_stop(struct seq_file *s, void *v) { /* No-op */ } /* * Seq_file read operations for /smack/load */ static void *load_seq_start(struct seq_file *s, loff_t *pos) { return smk_seq_start(s, pos, &smack_rule_list); } static void *load_seq_next(struct seq_file *s, void *v, loff_t *pos) { return smk_seq_next(s, v, pos, &smack_rule_list); } 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); struct smack_rule *srp = smlp->smk_rule; seq_printf(s, "%s %s", (char *)srp->smk_subject, (char *)srp->smk_object); 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 == 0) seq_putc(s, '-'); seq_putc(s, '\n'); return 0; } static const struct seq_operations load_seq_ops = { .start = load_seq_start, .next = load_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 (!capable(CAP_MAC_ADMIN)) return -EPERM; return smk_write_load_list(file, buf, count, ppos, NULL, NULL); } 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); if (doip == NULL) panic("smack: Failed to initialize cipso DOI.\n"); 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); kfree(doip); 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); } rc = netlbl_cfg_unlbl_map_add(smack_net_ambient, 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(list, struct smack_known, list); struct smack_cipso *scp = skp->smk_cipso; char *cbp; char sep = '/'; int cat = 1; int i; unsigned char m; if (scp == NULL) return 0; seq_printf(s, "%s %3d", (char *)&skp->smk_known, scp->smk_level); cbp = scp->smk_catset; for (i = 0; i < SMK_LABELLEN; i++) for (m = 0x80; m != 0; m >>= 1) { if (m & cbp[i]) { seq_printf(s, "%c%d", sep, cat); sep = ','; } cat++; } 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_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) { struct smack_known *skp; struct smack_cipso *scp = NULL; char mapcatset[SMK_LABELLEN]; int maplevel; 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 (!capable(CAP_MAC_ADMIN)) return -EPERM; if (*ppos != 0) return -EINVAL; if (count < SMK_CIPSOMIN || count > SMK_CIPSOMAX) return -EINVAL; data = kzalloc(count + 1, GFP_KERNEL); if (data == NULL) return -ENOMEM; if (copy_from_user(data, buf, count) != 0) { rc = -EFAULT; goto unlockedout; } /* labels cannot begin with a '-' */ if (data[0] == '-') { rc = -EINVAL; goto unlockedout; } data[count] = '\0'; 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 (skp == NULL) goto out; rule += SMK_LABELLEN; ret = sscanf(rule, "%d", &maplevel); if (ret != 1 || maplevel > SMACK_CIPSO_MAXLEVEL) goto out; rule += SMK_DIGITLEN; ret = sscanf(rule, "%d", &catlen); if (ret != 1 || catlen > SMACK_CIPSO_MAXCATNUM) goto out; if (count != (SMK_CIPSOMIN + catlen * SMK_DIGITLEN)) goto out; memset(mapcatset, 0, sizeof(mapcatset)); for (i = 0; i < catlen; i++) { rule += SMK_DIGITLEN; ret = sscanf(rule, "%d", &cat); if (ret != 1 || cat > SMACK_CIPSO_MAXCATVAL) goto out; smack_catset_bit(cat, mapcatset); } if (skp->smk_cipso == NULL) { scp = kzalloc(sizeof(struct smack_cipso), GFP_KERNEL); if (scp == NULL) { rc = -ENOMEM; goto out; } } spin_lock_bh(&skp->smk_cipsolock); if (scp == NULL) scp = skp->smk_cipso; else skp->smk_cipso = scp; scp->smk_level = maplevel; memcpy(scp->smk_catset, mapcatset, sizeof(mapcatset)); spin_unlock_bh(&skp->smk_cipsolock); rc = count; out: mutex_unlock(&smack_cipso_lock); unlockedout: kfree(data); return rc; } 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/netlabel */ static void *netlbladdr_seq_start(struct seq_file *s, loff_t *pos) { return smk_seq_start(s, pos, &smk_netlbladdr_list); } static void *netlbladdr_seq_next(struct seq_file *s, void *v, loff_t *pos) { return smk_seq_next(s, v, pos, &smk_netlbladdr_list); } #define BEBITS (sizeof(__be32) * 8) /* * Print host/label pairs */ 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); unsigned char *hp = (char *) &skp->smk_host.sin_addr.s_addr; int maskn; u32 temp_mask = be32_to_cpu(skp->smk_mask.s_addr); for (maskn = 0; temp_mask; temp_mask <<= 1, maskn++); seq_printf(s, "%u.%u.%u.%u/%d %s\n", hp[0], hp[1], hp[2], hp[3], maskn, skp->smk_label); return 0; } static const struct seq_operations netlbladdr_seq_ops = { .start = netlbladdr_seq_start, .next = netlbladdr_seq_next, .show = netlbladdr_seq_show, .stop = smk_seq_stop, }; /** * smk_open_netlbladdr - open() for /smack/netlabel * @inode: inode structure representing file * @file: "netlabel" file pointer * * Connect our netlbladdr_seq_* operations with /smack/netlabel * file_operations */ static int smk_open_netlbladdr(struct inode *inode, struct file *file) { return seq_open(file, &netlbladdr_seq_ops); } /** * smk_netlbladdr_insert * @new : netlabel to insert * * This helper insert netlabel in the smack_netlbladdrs list * sorted by netmask length (longest to smallest) * locked by &smk_netlbladdr_lock in smk_write_netlbladdr * */ static void smk_netlbladdr_insert(struct smk_netlbladdr *new) { struct smk_netlbladdr *m, *m_next; if (list_empty(&smk_netlbladdr_list)) { list_add_rcu(&new->list, &smk_netlbladdr_list); return; } m = list_entry_rcu(smk_netlbladdr_list.next, struct smk_netlbladdr, list); /* the comparison '>' is a bit hacky, but works */ if (new->smk_mask.s_addr > m->smk_mask.s_addr) { list_add_rcu(&new->list, &smk_netlbladdr_list); return; } list_for_each_entry_rcu(m, &smk_netlbladdr_list, list) { if (list_is_last(&m->list, &smk_netlbladdr_list)) { list_add_rcu(&new->list, &m->list); return; } m_next = list_entry_rcu(m->list.next, struct smk_netlbladdr, list); if (new->smk_mask.s_addr > m_next->smk_mask.s_addr) { list_add_rcu(&new->list, &m->list); return; } } } /** * smk_write_netlbladdr - 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 netlbladdr per write call. * Returns number of bytes written or error code, as appropriate */ static ssize_t smk_write_netlbladdr(struct file *file, const char __user *buf, size_t count, loff_t *ppos) { struct smk_netlbladdr *skp; struct sockaddr_in newname; char smack[SMK_LABELLEN]; char *sp; char data[SMK_NETLBLADDRMAX + 1]; char *host = (char *)&newname.sin_addr.s_addr; int rc; struct netlbl_audit audit_info; struct in_addr mask; unsigned int m; int found; u32 mask_bits = (1<<31); __be32 nsa; u32 temp_mask; /* * Must have privilege. * No partial writes. * Enough data must be present. * "