/* * 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 "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 */ }; /* * 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. */ struct smk_netlbladdr *smack_netlbladdrs; static int smk_cipso_doi_value = SMACK_CIPSO_DOI_DEFAULT; struct smk_list_entry *smack_list; #define SEQ_READ_FINISHED 1 /* * 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_ACCESS "rwxa" #define SMK_ACCESSLEN (sizeof(SMK_ACCESS) - 1) #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(current_security()); } /* * 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 /* * Seq_file read operations for /smack/load */ static void *load_seq_start(struct seq_file *s, loff_t *pos) { if (*pos == SEQ_READ_FINISHED) return NULL; return smack_list; } static void *load_seq_next(struct seq_file *s, void *v, loff_t *pos) { struct smk_list_entry *skp = ((struct smk_list_entry *) v)->smk_next; if (skp == NULL) *pos = SEQ_READ_FINISHED; return skp; } static int load_seq_show(struct seq_file *s, void *v) { struct smk_list_entry *slp = (struct smk_list_entry *) v; struct smack_rule *srp = &slp->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 == 0) seq_putc(s, '-'); seq_putc(s, '\n'); return 0; } static void load_seq_stop(struct seq_file *s, void *v) { /* No-op */ } static struct seq_operations load_seq_ops = { .start = load_seq_start, .next = load_seq_next, .show = load_seq_show, .stop = load_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_set_access - add a rule to the rule list * @srp: the new rule to add * * 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_rule *srp) { struct smk_list_entry *sp; struct smk_list_entry *newp; int ret = 0; mutex_lock(&smack_list_lock); for (sp = smack_list; sp != NULL; sp = sp->smk_next) if (sp->smk_rule.smk_subject == srp->smk_subject && sp->smk_rule.smk_object == srp->smk_object) { sp->smk_rule.smk_access = srp->smk_access; break; } if (sp == NULL) { newp = kzalloc(sizeof(struct smk_list_entry), GFP_KERNEL); if (newp == NULL) { ret = -ENOMEM; goto out; } newp->smk_rule = *srp; newp->smk_next = smack_list; smack_list = newp; } out: mutex_unlock(&smack_list_lock); return ret; } /** * 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 * * 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(struct file *file, const char __user *buf, size_t count, loff_t *ppos) { struct smack_rule rule; char *data; int rc = -EINVAL; /* * 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_LOADLEN) return -EINVAL; data = kzalloc(count, GFP_KERNEL); if (data == NULL) return -ENOMEM; if (copy_from_user(data, buf, count) != 0) { rc = -EFAULT; goto out; } rule.smk_subject = smk_import(data, 0); if (rule.smk_subject == NULL) goto out; rule.smk_object = smk_import(data + SMK_LABELLEN, 0); if (rule.smk_object == NULL) goto out; rule.smk_access = 0; switch (data[SMK_LABELLEN + SMK_LABELLEN]) { case '-': break; case 'r': case 'R': rule.smk_access |= MAY_READ; break; default: goto out; } switch (data[SMK_LABELLEN + SMK_LABELLEN + 1]) { case '-': break; case 'w': case 'W': rule.smk_access |= MAY_WRITE; break; default: goto out; } switch (data[SMK_LABELLEN + SMK_LABELLEN + 2]) { case '-': break; case 'x': case 'X': rule.smk_access |= MAY_EXEC; break; default: goto out; } switch (data[SMK_LABELLEN + SMK_LABELLEN + 3]) { case '-': break; case 'a': case 'A': rule.smk_access |= MAY_APPEND; break; default: goto out; } rc = smk_set_access(&rule); if (!rc) rc = count; out: kfree(data); return rc; } 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) { if (*pos == SEQ_READ_FINISHED) return NULL; return smack_known; } static void *cipso_seq_next(struct seq_file *s, void *v, loff_t *pos) { struct smack_known *skp = ((struct smack_known *) v)->smk_next; /* * Omit labels with no associated cipso value */ while (skp != NULL && !skp->smk_cipso) skp = skp->smk_next; if (skp == NULL) *pos = SEQ_READ_FINISHED; return skp; } /* * Print cipso labels in format: * label level[/cat[,cat]] */ static int cipso_seq_show(struct seq_file *s, void *v) { struct smack_known *skp = (struct smack_known *) v; 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 void cipso_seq_stop(struct seq_file *s, void *v) { /* No-op */ } static struct seq_operations cipso_seq_ops = { .start = cipso_seq_start, .stop = cipso_seq_stop, .next = cipso_seq_next, .show = cipso_seq_show, }; /** * 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; } 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) { if (*pos == SEQ_READ_FINISHED) return NULL; return smack_netlbladdrs; } static void *netlbladdr_seq_next(struct seq_file *s, void *v, loff_t *pos) { struct smk_netlbladdr *skp = ((struct smk_netlbladdr *) v)->smk_next; if (skp == NULL) *pos = SEQ_READ_FINISHED; return skp; } /* #define BEMASK 0x80000000 */ #define BEMASK 0x00000001 #define BEBITS (sizeof(__be32) * 8) /* * Print host/label pairs */ static int netlbladdr_seq_show(struct seq_file *s, void *v) { struct smk_netlbladdr *skp = (struct smk_netlbladdr *) v; unsigned char *hp = (char *) &skp->smk_host.sin_addr.s_addr; __be32 bebits; int maskn = 0; for (bebits = BEMASK; bebits != 0; maskn++, bebits <<= 1) if ((skp->smk_mask.s_addr & bebits) == 0) break; 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 void netlbladdr_seq_stop(struct seq_file *s, void *v) { /* No-op */ } static struct seq_operations netlbladdr_seq_ops = { .start = netlbladdr_seq_start, .stop = netlbladdr_seq_stop, .next = netlbladdr_seq_next, .show = netlbladdr_seq_show, }; /** * 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_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]; char *host = (char *)&newname.sin_addr.s_addr; int rc; struct netlbl_audit audit_info; struct in_addr mask; unsigned int m; __be32 bebits = BEMASK; __be32 nsa; /* * Must have privilege. * No partial writes. * Enough data must be present. * "