diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2015-09-08 12:41:25 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2015-09-08 12:41:25 -0700 |
commit | b793c005ceabf6db0b17494b0ec67ade6796bb34 (patch) | |
tree | 080c884f04254403ec9564742f591a9fd9b7e95a /security | |
parent | 6f0a2fc1feb19bd142961a39dc118e7e55418b3f (diff) | |
parent | 07f081fb5057b2ea98baeca3a47bf0eb33e94aa1 (diff) | |
download | lwn-b793c005ceabf6db0b17494b0ec67ade6796bb34.tar.gz lwn-b793c005ceabf6db0b17494b0ec67ade6796bb34.zip |
Merge branch 'next' of git://git.kernel.org/pub/scm/linux/kernel/git/jmorris/linux-security
Pull security subsystem updates from James Morris:
"Highlights:
- PKCS#7 support added to support signed kexec, also utilized for
module signing. See comments in 3f1e1bea.
** NOTE: this requires linking against the OpenSSL library, which
must be installed, e.g. the openssl-devel on Fedora **
- Smack
- add IPv6 host labeling; ignore labels on kernel threads
- support smack labeling mounts which use binary mount data
- SELinux:
- add ioctl whitelisting (see
http://kernsec.org/files/lss2015/vanderstoep.pdf)
- fix mprotect PROT_EXEC regression caused by mm change
- Seccomp:
- add ptrace options for suspend/resume"
* 'next' of git://git.kernel.org/pub/scm/linux/kernel/git/jmorris/linux-security: (57 commits)
PKCS#7: Add OIDs for sha224, sha284 and sha512 hash algos and use them
Documentation/Changes: Now need OpenSSL devel packages for module signing
scripts: add extract-cert and sign-file to .gitignore
modsign: Handle signing key in source tree
modsign: Use if_changed rule for extracting cert from module signing key
Move certificate handling to its own directory
sign-file: Fix warning about BIO_reset() return value
PKCS#7: Add MODULE_LICENSE() to test module
Smack - Fix build error with bringup unconfigured
sign-file: Document dependency on OpenSSL devel libraries
PKCS#7: Appropriately restrict authenticated attributes and content type
KEYS: Add a name for PKEY_ID_PKCS7
PKCS#7: Improve and export the X.509 ASN.1 time object decoder
modsign: Use extract-cert to process CONFIG_SYSTEM_TRUSTED_KEYS
extract-cert: Cope with multiple X.509 certificates in a single file
sign-file: Generate CMS message as signature instead of PKCS#7
PKCS#7: Support CMS messages also [RFC5652]
X.509: Change recorded SKID & AKID to not include Subject or Issuer
PKCS#7: Check content type and versions
MAINTAINERS: The keyrings mailing list has moved
...
Diffstat (limited to 'security')
-rw-r--r-- | security/Kconfig | 5 | ||||
-rw-r--r-- | security/lsm_audit.c | 15 | ||||
-rw-r--r-- | security/security.c | 11 | ||||
-rw-r--r-- | security/selinux/avc.c | 418 | ||||
-rw-r--r-- | security/selinux/hooks.c | 147 | ||||
-rw-r--r-- | security/selinux/include/avc.h | 6 | ||||
-rw-r--r-- | security/selinux/include/security.h | 32 | ||||
-rw-r--r-- | security/selinux/ss/avtab.c | 104 | ||||
-rw-r--r-- | security/selinux/ss/avtab.h | 33 | ||||
-rw-r--r-- | security/selinux/ss/conditional.c | 32 | ||||
-rw-r--r-- | security/selinux/ss/conditional.h | 6 | ||||
-rw-r--r-- | security/selinux/ss/policydb.c | 5 | ||||
-rw-r--r-- | security/selinux/ss/services.c | 213 | ||||
-rw-r--r-- | security/selinux/ss/services.h | 6 | ||||
-rw-r--r-- | security/smack/smack.h | 66 | ||||
-rw-r--r-- | security/smack/smack_access.c | 6 | ||||
-rw-r--r-- | security/smack/smack_lsm.c | 511 | ||||
-rw-r--r-- | security/smack/smackfs.c | 436 | ||||
-rw-r--r-- | security/yama/Kconfig | 9 | ||||
-rw-r--r-- | security/yama/yama_lsm.c | 33 |
20 files changed, 1732 insertions, 362 deletions
diff --git a/security/Kconfig b/security/Kconfig index bf4ec46474b6..e45237897b43 100644 --- a/security/Kconfig +++ b/security/Kconfig @@ -132,7 +132,6 @@ choice default DEFAULT_SECURITY_SMACK if SECURITY_SMACK default DEFAULT_SECURITY_TOMOYO if SECURITY_TOMOYO default DEFAULT_SECURITY_APPARMOR if SECURITY_APPARMOR - default DEFAULT_SECURITY_YAMA if SECURITY_YAMA default DEFAULT_SECURITY_DAC help @@ -151,9 +150,6 @@ choice config DEFAULT_SECURITY_APPARMOR bool "AppArmor" if SECURITY_APPARMOR=y - config DEFAULT_SECURITY_YAMA - bool "Yama" if SECURITY_YAMA=y - config DEFAULT_SECURITY_DAC bool "Unix Discretionary Access Controls" @@ -165,7 +161,6 @@ config DEFAULT_SECURITY default "smack" if DEFAULT_SECURITY_SMACK default "tomoyo" if DEFAULT_SECURITY_TOMOYO default "apparmor" if DEFAULT_SECURITY_APPARMOR - default "yama" if DEFAULT_SECURITY_YAMA default "" if DEFAULT_SECURITY_DAC endmenu diff --git a/security/lsm_audit.c b/security/lsm_audit.c index 4ed98107ace3..cccbf3068cdc 100644 --- a/security/lsm_audit.c +++ b/security/lsm_audit.c @@ -245,6 +245,21 @@ static void dump_common_audit_data(struct audit_buffer *ab, } break; } + case LSM_AUDIT_DATA_IOCTL_OP: { + struct inode *inode; + + audit_log_d_path(ab, " path=", &a->u.op->path); + + inode = a->u.op->path.dentry->d_inode; + if (inode) { + audit_log_format(ab, " dev="); + audit_log_untrustedstring(ab, inode->i_sb->s_id); + audit_log_format(ab, " ino=%lu", inode->i_ino); + } + + audit_log_format(ab, " ioctlcmd=%hx", a->u.op->cmd); + break; + } case LSM_AUDIT_DATA_DENTRY: { struct inode *inode; diff --git a/security/security.c b/security/security.c index 75b85fdc4e97..46f405ce6b0f 100644 --- a/security/security.c +++ b/security/security.c @@ -56,18 +56,13 @@ int __init security_init(void) pr_info("Security Framework initialized\n"); /* - * Always load the capability module. + * Load minor LSMs, with the capability module always first. */ capability_add_hooks(); -#ifdef CONFIG_SECURITY_YAMA_STACKED - /* - * If Yama is configured for stacking load it next. - */ yama_add_hooks(); -#endif + /* - * Load the chosen module if there is one. - * This will also find yama if it is stacking + * Load all the remaining security modules. */ do_security_initcalls(); diff --git a/security/selinux/avc.c b/security/selinux/avc.c index 0b122b1421a9..e60c79de13e1 100644 --- a/security/selinux/avc.c +++ b/security/selinux/avc.c @@ -22,6 +22,7 @@ #include <linux/init.h> #include <linux/skbuff.h> #include <linux/percpu.h> +#include <linux/list.h> #include <net/sock.h> #include <linux/un.h> #include <net/af_unix.h> @@ -48,6 +49,7 @@ struct avc_entry { u32 tsid; u16 tclass; struct av_decision avd; + struct avc_xperms_node *xp_node; }; struct avc_node { @@ -56,6 +58,16 @@ struct avc_node { struct rcu_head rhead; }; +struct avc_xperms_decision_node { + struct extended_perms_decision xpd; + struct list_head xpd_list; /* list of extended_perms_decision */ +}; + +struct avc_xperms_node { + struct extended_perms xp; + struct list_head xpd_head; /* list head of extended_perms_decision */ +}; + struct avc_cache { struct hlist_head slots[AVC_CACHE_SLOTS]; /* head for avc_node->list */ spinlock_t slots_lock[AVC_CACHE_SLOTS]; /* lock for writes */ @@ -80,6 +92,9 @@ DEFINE_PER_CPU(struct avc_cache_stats, avc_cache_stats) = { 0 }; static struct avc_cache avc_cache; static struct avc_callback_node *avc_callbacks; static struct kmem_cache *avc_node_cachep; +static struct kmem_cache *avc_xperms_data_cachep; +static struct kmem_cache *avc_xperms_decision_cachep; +static struct kmem_cache *avc_xperms_cachep; static inline int avc_hash(u32 ssid, u32 tsid, u16 tclass) { @@ -101,6 +116,7 @@ static void avc_dump_av(struct audit_buffer *ab, u16 tclass, u32 av) return; } + BUG_ON(!tclass || tclass >= ARRAY_SIZE(secclass_map)); perms = secclass_map[tclass-1].perms; audit_log_format(ab, " {"); @@ -149,7 +165,7 @@ static void avc_dump_query(struct audit_buffer *ab, u32 ssid, u32 tsid, u16 tcla kfree(scontext); } - BUG_ON(tclass >= ARRAY_SIZE(secclass_map)); + BUG_ON(!tclass || tclass >= ARRAY_SIZE(secclass_map)); audit_log_format(ab, " tclass=%s", secclass_map[tclass-1].name); } @@ -170,7 +186,17 @@ void __init avc_init(void) atomic_set(&avc_cache.lru_hint, 0); avc_node_cachep = kmem_cache_create("avc_node", sizeof(struct avc_node), - 0, SLAB_PANIC, NULL); + 0, SLAB_PANIC, NULL); + avc_xperms_cachep = kmem_cache_create("avc_xperms_node", + sizeof(struct avc_xperms_node), + 0, SLAB_PANIC, NULL); + avc_xperms_decision_cachep = kmem_cache_create( + "avc_xperms_decision_node", + sizeof(struct avc_xperms_decision_node), + 0, SLAB_PANIC, NULL); + avc_xperms_data_cachep = kmem_cache_create("avc_xperms_data", + sizeof(struct extended_perms_data), + 0, SLAB_PANIC, NULL); audit_log(current->audit_context, GFP_KERNEL, AUDIT_KERNEL, "AVC INITIALIZED\n"); } @@ -205,9 +231,261 @@ int avc_get_hash_stats(char *page) slots_used, AVC_CACHE_SLOTS, max_chain_len); } +/* + * using a linked list for extended_perms_decision lookup because the list is + * always small. i.e. less than 5, typically 1 + */ +static struct extended_perms_decision *avc_xperms_decision_lookup(u8 driver, + struct avc_xperms_node *xp_node) +{ + struct avc_xperms_decision_node *xpd_node; + + list_for_each_entry(xpd_node, &xp_node->xpd_head, xpd_list) { + if (xpd_node->xpd.driver == driver) + return &xpd_node->xpd; + } + return NULL; +} + +static inline unsigned int +avc_xperms_has_perm(struct extended_perms_decision *xpd, + u8 perm, u8 which) +{ + unsigned int rc = 0; + + if ((which == XPERMS_ALLOWED) && + (xpd->used & XPERMS_ALLOWED)) + rc = security_xperm_test(xpd->allowed->p, perm); + else if ((which == XPERMS_AUDITALLOW) && + (xpd->used & XPERMS_AUDITALLOW)) + rc = security_xperm_test(xpd->auditallow->p, perm); + else if ((which == XPERMS_DONTAUDIT) && + (xpd->used & XPERMS_DONTAUDIT)) + rc = security_xperm_test(xpd->dontaudit->p, perm); + return rc; +} + +static void avc_xperms_allow_perm(struct avc_xperms_node *xp_node, + u8 driver, u8 perm) +{ + struct extended_perms_decision *xpd; + security_xperm_set(xp_node->xp.drivers.p, driver); + xpd = avc_xperms_decision_lookup(driver, xp_node); + if (xpd && xpd->allowed) + security_xperm_set(xpd->allowed->p, perm); +} + +static void avc_xperms_decision_free(struct avc_xperms_decision_node *xpd_node) +{ + struct extended_perms_decision *xpd; + + xpd = &xpd_node->xpd; + if (xpd->allowed) + kmem_cache_free(avc_xperms_data_cachep, xpd->allowed); + if (xpd->auditallow) + kmem_cache_free(avc_xperms_data_cachep, xpd->auditallow); + if (xpd->dontaudit) + kmem_cache_free(avc_xperms_data_cachep, xpd->dontaudit); + kmem_cache_free(avc_xperms_decision_cachep, xpd_node); +} + +static void avc_xperms_free(struct avc_xperms_node *xp_node) +{ + struct avc_xperms_decision_node *xpd_node, *tmp; + + if (!xp_node) + return; + + list_for_each_entry_safe(xpd_node, tmp, &xp_node->xpd_head, xpd_list) { + list_del(&xpd_node->xpd_list); + avc_xperms_decision_free(xpd_node); + } + kmem_cache_free(avc_xperms_cachep, xp_node); +} + +static void avc_copy_xperms_decision(struct extended_perms_decision *dest, + struct extended_perms_decision *src) +{ + dest->driver = src->driver; + dest->used = src->used; + if (dest->used & XPERMS_ALLOWED) + memcpy(dest->allowed->p, src->allowed->p, + sizeof(src->allowed->p)); + if (dest->used & XPERMS_AUDITALLOW) + memcpy(dest->auditallow->p, src->auditallow->p, + sizeof(src->auditallow->p)); + if (dest->used & XPERMS_DONTAUDIT) + memcpy(dest->dontaudit->p, src->dontaudit->p, + sizeof(src->dontaudit->p)); +} + +/* + * similar to avc_copy_xperms_decision, but only copy decision + * information relevant to this perm + */ +static inline void avc_quick_copy_xperms_decision(u8 perm, + struct extended_perms_decision *dest, + struct extended_perms_decision *src) +{ + /* + * compute index of the u32 of the 256 bits (8 u32s) that contain this + * command permission + */ + u8 i = perm >> 5; + + dest->used = src->used; + if (dest->used & XPERMS_ALLOWED) + dest->allowed->p[i] = src->allowed->p[i]; + if (dest->used & XPERMS_AUDITALLOW) + dest->auditallow->p[i] = src->auditallow->p[i]; + if (dest->used & XPERMS_DONTAUDIT) + dest->dontaudit->p[i] = src->dontaudit->p[i]; +} + +static struct avc_xperms_decision_node + *avc_xperms_decision_alloc(u8 which) +{ + struct avc_xperms_decision_node *xpd_node; + struct extended_perms_decision *xpd; + + xpd_node = kmem_cache_zalloc(avc_xperms_decision_cachep, + GFP_ATOMIC | __GFP_NOMEMALLOC); + if (!xpd_node) + return NULL; + + xpd = &xpd_node->xpd; + if (which & XPERMS_ALLOWED) { + xpd->allowed = kmem_cache_zalloc(avc_xperms_data_cachep, + GFP_ATOMIC | __GFP_NOMEMALLOC); + if (!xpd->allowed) + goto error; + } + if (which & XPERMS_AUDITALLOW) { + xpd->auditallow = kmem_cache_zalloc(avc_xperms_data_cachep, + GFP_ATOMIC | __GFP_NOMEMALLOC); + if (!xpd->auditallow) + goto error; + } + if (which & XPERMS_DONTAUDIT) { + xpd->dontaudit = kmem_cache_zalloc(avc_xperms_data_cachep, + GFP_ATOMIC | __GFP_NOMEMALLOC); + if (!xpd->dontaudit) + goto error; + } + return xpd_node; +error: + avc_xperms_decision_free(xpd_node); + return NULL; +} + +static int avc_add_xperms_decision(struct avc_node *node, + struct extended_perms_decision *src) +{ + struct avc_xperms_decision_node *dest_xpd; + + node->ae.xp_node->xp.len++; + dest_xpd = avc_xperms_decision_alloc(src->used); + if (!dest_xpd) + return -ENOMEM; + avc_copy_xperms_decision(&dest_xpd->xpd, src); + list_add(&dest_xpd->xpd_list, &node->ae.xp_node->xpd_head); + return 0; +} + +static struct avc_xperms_node *avc_xperms_alloc(void) +{ + struct avc_xperms_node *xp_node; + + xp_node = kmem_cache_zalloc(avc_xperms_cachep, + GFP_ATOMIC|__GFP_NOMEMALLOC); + if (!xp_node) + return xp_node; + INIT_LIST_HEAD(&xp_node->xpd_head); + return xp_node; +} + +static int avc_xperms_populate(struct avc_node *node, + struct avc_xperms_node *src) +{ + struct avc_xperms_node *dest; + struct avc_xperms_decision_node *dest_xpd; + struct avc_xperms_decision_node *src_xpd; + + if (src->xp.len == 0) + return 0; + dest = avc_xperms_alloc(); + if (!dest) + return -ENOMEM; + + memcpy(dest->xp.drivers.p, src->xp.drivers.p, sizeof(dest->xp.drivers.p)); + dest->xp.len = src->xp.len; + + /* for each source xpd allocate a destination xpd and copy */ + list_for_each_entry(src_xpd, &src->xpd_head, xpd_list) { + dest_xpd = avc_xperms_decision_alloc(src_xpd->xpd.used); + if (!dest_xpd) + goto error; + avc_copy_xperms_decision(&dest_xpd->xpd, &src_xpd->xpd); + list_add(&dest_xpd->xpd_list, &dest->xpd_head); + } + node->ae.xp_node = dest; + return 0; +error: + avc_xperms_free(dest); + return -ENOMEM; + +} + +static inline u32 avc_xperms_audit_required(u32 requested, + struct av_decision *avd, + struct extended_perms_decision *xpd, + u8 perm, + int result, + u32 *deniedp) +{ + u32 denied, audited; + + denied = requested & ~avd->allowed; + if (unlikely(denied)) { + audited = denied & avd->auditdeny; + if (audited && xpd) { + if (avc_xperms_has_perm(xpd, perm, XPERMS_DONTAUDIT)) + audited &= ~requested; + } + } else if (result) { + audited = denied = requested; + } else { + audited = requested & avd->auditallow; + if (audited && xpd) { + if (!avc_xperms_has_perm(xpd, perm, XPERMS_AUDITALLOW)) + audited &= ~requested; + } + } + + *deniedp = denied; + return audited; +} + +static inline int avc_xperms_audit(u32 ssid, u32 tsid, u16 tclass, + u32 requested, struct av_decision *avd, + struct extended_perms_decision *xpd, + u8 perm, int result, + struct common_audit_data *ad) +{ + u32 audited, denied; + + audited = avc_xperms_audit_required( + requested, avd, xpd, perm, result, &denied); + if (likely(!audited)) + return 0; + return slow_avc_audit(ssid, tsid, tclass, requested, + audited, denied, result, ad, 0); +} + static void avc_node_free(struct rcu_head *rhead) { struct avc_node *node = container_of(rhead, struct avc_node, rhead); + avc_xperms_free(node->ae.xp_node); kmem_cache_free(avc_node_cachep, node); avc_cache_stats_incr(frees); } @@ -221,6 +499,7 @@ static void avc_node_delete(struct avc_node *node) static void avc_node_kill(struct avc_node *node) { + avc_xperms_free(node->ae.xp_node); kmem_cache_free(avc_node_cachep, node); avc_cache_stats_incr(frees); atomic_dec(&avc_cache.active_nodes); @@ -367,6 +646,7 @@ static int avc_latest_notif_update(int seqno, int is_insert) * @tsid: target security identifier * @tclass: target security class * @avd: resulting av decision + * @xp_node: resulting extended permissions * * Insert an AVC entry for the SID pair * (@ssid, @tsid) and class @tclass. @@ -378,7 +658,9 @@ static int avc_latest_notif_update(int seqno, int is_insert) * the access vectors into a cache entry, returns * avc_node inserted. Otherwise, this function returns NULL. */ -static struct avc_node *avc_insert(u32 ssid, u32 tsid, u16 tclass, struct av_decision *avd) +static struct avc_node *avc_insert(u32 ssid, u32 tsid, u16 tclass, + struct av_decision *avd, + struct avc_xperms_node *xp_node) { struct avc_node *pos, *node = NULL; int hvalue; @@ -391,10 +673,15 @@ static struct avc_node *avc_insert(u32 ssid, u32 tsid, u16 tclass, struct av_dec if (node) { struct hlist_head *head; spinlock_t *lock; + int rc = 0; hvalue = avc_hash(ssid, tsid, tclass); avc_node_populate(node, ssid, tsid, tclass, avd); - + rc = avc_xperms_populate(node, xp_node); + if (rc) { + kmem_cache_free(avc_node_cachep, node); + return NULL; + } head = &avc_cache.slots[hvalue]; lock = &avc_cache.slots_lock[hvalue]; @@ -523,14 +810,17 @@ out: * @perms : Permission mask bits * @ssid,@tsid,@tclass : identifier of an AVC entry * @seqno : sequence number when decision was made + * @xpd: extended_perms_decision to be added to the node * * if a valid AVC entry doesn't exist,this function returns -ENOENT. * if kmalloc() called internal returns NULL, this function returns -ENOMEM. * otherwise, this function updates the AVC entry. The original AVC-entry object * will release later by RCU. */ -static int avc_update_node(u32 event, u32 perms, u32 ssid, u32 tsid, u16 tclass, - u32 seqno) +static int avc_update_node(u32 event, u32 perms, u8 driver, u8 xperm, u32 ssid, + u32 tsid, u16 tclass, u32 seqno, + struct extended_perms_decision *xpd, + u32 flags) { int hvalue, rc = 0; unsigned long flag; @@ -574,9 +864,19 @@ static int avc_update_node(u32 event, u32 perms, u32 ssid, u32 tsid, u16 tclass, avc_node_populate(node, ssid, tsid, tclass, &orig->ae.avd); + if (orig->ae.xp_node) { + rc = avc_xperms_populate(node, orig->ae.xp_node); + if (rc) { + kmem_cache_free(avc_node_cachep, node); + goto out_unlock; + } + } + switch (event) { case AVC_CALLBACK_GRANT: node->ae.avd.allowed |= perms; + if (node->ae.xp_node && (flags & AVC_EXTENDED_PERMS)) + avc_xperms_allow_perm(node->ae.xp_node, driver, xperm); break; case AVC_CALLBACK_TRY_REVOKE: case AVC_CALLBACK_REVOKE: @@ -594,6 +894,9 @@ static int avc_update_node(u32 event, u32 perms, u32 ssid, u32 tsid, u16 tclass, case AVC_CALLBACK_AUDITDENY_DISABLE: node->ae.avd.auditdeny &= ~perms; break; + case AVC_CALLBACK_ADD_XPERMS: + avc_add_xperms_decision(node, xpd); + break; } avc_node_replace(node, orig); out_unlock: @@ -665,18 +968,20 @@ int avc_ss_reset(u32 seqno) * results in a bigger stack frame. */ static noinline struct avc_node *avc_compute_av(u32 ssid, u32 tsid, - u16 tclass, struct av_decision *avd) + u16 tclass, struct av_decision *avd, + struct avc_xperms_node *xp_node) { rcu_read_unlock(); - security_compute_av(ssid, tsid, tclass, avd); + INIT_LIST_HEAD(&xp_node->xpd_head); + security_compute_av(ssid, tsid, tclass, avd, &xp_node->xp); rcu_read_lock(); - return avc_insert(ssid, tsid, tclass, avd); + return avc_insert(ssid, tsid, tclass, avd, xp_node); } static noinline int avc_denied(u32 ssid, u32 tsid, - u16 tclass, u32 requested, - unsigned flags, - struct av_decision *avd) + u16 tclass, u32 requested, + u8 driver, u8 xperm, unsigned flags, + struct av_decision *avd) { if (flags & AVC_STRICT) return -EACCES; @@ -684,11 +989,91 @@ static noinline int avc_denied(u32 ssid, u32 tsid, if (selinux_enforcing && !(avd->flags & AVD_FLAGS_PERMISSIVE)) return -EACCES; - avc_update_node(AVC_CALLBACK_GRANT, requested, ssid, - tsid, tclass, avd->seqno); + avc_update_node(AVC_CALLBACK_GRANT, requested, driver, xperm, ssid, + tsid, tclass, avd->seqno, NULL, flags); return 0; } +/* + * The avc extended permissions logic adds an additional 256 bits of + * permissions to an avc node when extended permissions for that node are + * specified in the avtab. If the additional 256 permissions is not adequate, + * as-is the case with ioctls, then multiple may be chained together and the + * driver field is used to specify which set contains the permission. + */ +int avc_has_extended_perms(u32 ssid, u32 tsid, u16 tclass, u32 requested, + u8 driver, u8 xperm, struct common_audit_data *ad) +{ + struct avc_node *node; + struct av_decision avd; + u32 denied; + struct extended_perms_decision local_xpd; + struct extended_perms_decision *xpd = NULL; + struct extended_perms_data allowed; + struct extended_perms_data auditallow; + struct extended_perms_data dontaudit; + struct avc_xperms_node local_xp_node; + struct avc_xperms_node *xp_node; + int rc = 0, rc2; + + xp_node = &local_xp_node; + BUG_ON(!requested); + + rcu_read_lock(); + + node = avc_lookup(ssid, tsid, tclass); + if (unlikely(!node)) { + node = avc_compute_av(ssid, tsid, tclass, &avd, xp_node); + } else { + memcpy(&avd, &node->ae.avd, sizeof(avd)); + xp_node = node->ae.xp_node; + } + /* if extended permissions are not defined, only consider av_decision */ + if (!xp_node || !xp_node->xp.len) + goto decision; + + local_xpd.allowed = &allowed; + local_xpd.auditallow = &auditallow; + local_xpd.dontaudit = &dontaudit; + + xpd = avc_xperms_decision_lookup(driver, xp_node); + if (unlikely(!xpd)) { + /* + * Compute the extended_perms_decision only if the driver + * is flagged + */ + if (!security_xperm_test(xp_node->xp.drivers.p, driver)) { + avd.allowed &= ~requested; + goto decision; + } + rcu_read_unlock(); + security_compute_xperms_decision(ssid, tsid, tclass, driver, + &local_xpd); + rcu_read_lock(); + avc_update_node(AVC_CALLBACK_ADD_XPERMS, requested, driver, xperm, + ssid, tsid, tclass, avd.seqno, &local_xpd, 0); + } else { + avc_quick_copy_xperms_decision(xperm, &local_xpd, xpd); + } + xpd = &local_xpd; + + if (!avc_xperms_has_perm(xpd, xperm, XPERMS_ALLOWED)) + avd.allowed &= ~requested; + +decision: + denied = requested & ~(avd.allowed); + if (unlikely(denied)) + rc = avc_denied(ssid, tsid, tclass, requested, driver, xperm, + AVC_EXTENDED_PERMS, &avd); + + rcu_read_unlock(); + + rc2 = avc_xperms_audit(ssid, tsid, tclass, requested, + &avd, xpd, xperm, rc, ad); + if (rc2) + return rc2; + return rc; +} /** * avc_has_perm_noaudit - Check permissions but perform no auditing. @@ -716,6 +1101,7 @@ inline int avc_has_perm_noaudit(u32 ssid, u32 tsid, struct av_decision *avd) { struct avc_node *node; + struct avc_xperms_node xp_node; int rc = 0; u32 denied; @@ -725,13 +1111,13 @@ inline int avc_has_perm_noaudit(u32 ssid, u32 tsid, node = avc_lookup(ssid, tsid, tclass); if (unlikely(!node)) - node = avc_compute_av(ssid, tsid, tclass, avd); + node = avc_compute_av(ssid, tsid, tclass, avd, &xp_node); else memcpy(avd, &node->ae.avd, sizeof(*avd)); denied = requested & ~(avd->allowed); if (unlikely(denied)) - rc = avc_denied(ssid, tsid, tclass, requested, flags, avd); + rc = avc_denied(ssid, tsid, tclass, requested, 0, 0, flags, avd); rcu_read_unlock(); return rc; diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index cdf4c589a391..e4369d86e588 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -254,10 +254,21 @@ static void inode_free_security(struct inode *inode) struct inode_security_struct *isec = inode->i_security; struct superblock_security_struct *sbsec = inode->i_sb->s_security; - spin_lock(&sbsec->isec_lock); - if (!list_empty(&isec->list)) + /* + * As not all inode security structures are in a list, we check for + * empty list outside of the lock to make sure that we won't waste + * time taking a lock doing nothing. + * + * The list_del_init() function can be safely called more than once. + * It should not be possible for this function to be called with + * concurrent list_add(), but for better safety against future changes + * in the code, we use list_empty_careful() here. + */ + if (!list_empty_careful(&isec->list)) { + spin_lock(&sbsec->isec_lock); list_del_init(&isec->list); - spin_unlock(&sbsec->isec_lock); + spin_unlock(&sbsec->isec_lock); + } /* * The inode may still be referenced in a path walk and @@ -1698,6 +1709,32 @@ out: return rc; } +/* + * Determine the label for an inode that might be unioned. + */ +static int selinux_determine_inode_label(const struct inode *dir, + const struct qstr *name, + u16 tclass, + u32 *_new_isid) +{ + const struct superblock_security_struct *sbsec = dir->i_sb->s_security; + const struct inode_security_struct *dsec = dir->i_security; + const struct task_security_struct *tsec = current_security(); + + if ((sbsec->flags & SE_SBINITIALIZED) && + (sbsec->behavior == SECURITY_FS_USE_MNTPOINT)) { + *_new_isid = sbsec->mntpoint_sid; + } else if ((sbsec->flags & SBLABEL_MNT) && + tsec->create_sid) { + *_new_isid = tsec->create_sid; + } else { + return security_transition_sid(tsec->sid, dsec->sid, tclass, + name, _new_isid); + } + + return 0; +} + /* Check whether a task can create a file. */ static int may_create(struct inode *dir, struct dentry *dentry, @@ -1714,7 +1751,6 @@ static int may_create(struct inode *dir, sbsec = dir->i_sb->s_security; sid = tsec->sid; - newsid = tsec->create_sid; ad.type = LSM_AUDIT_DATA_DENTRY; ad.u.dentry = dentry; @@ -1725,12 +1761,10 @@ static int may_create(struct inode *dir, if (rc) return rc; - if (!newsid || !(sbsec->flags & SBLABEL_MNT)) { - rc = security_transition_sid(sid, dsec->sid, tclass, - &dentry->d_name, &newsid); - if (rc) - return rc; - } + rc = selinux_determine_inode_label(dir, &dentry->d_name, tclass, + &newsid); + if (rc) + return rc; rc = avc_has_perm(sid, newsid, tclass, FILE__CREATE, &ad); if (rc) @@ -2704,32 +2738,14 @@ static int selinux_dentry_init_security(struct dentry *dentry, int mode, struct qstr *name, void **ctx, u32 *ctxlen) { - const struct cred *cred = current_cred(); - struct task_security_struct *tsec; - struct inode_security_struct *dsec; - struct superblock_security_struct *sbsec; - struct inode *dir = d_backing_inode(dentry->d_parent); u32 newsid; int rc; - tsec = cred->security; - dsec = dir->i_security; - sbsec = dir->i_sb->s_security; - - if (tsec->create_sid && sbsec->behavior != SECURITY_FS_USE_MNTPOINT) { - newsid = tsec->create_sid; - } else { - rc = security_transition_sid(tsec->sid, dsec->sid, - inode_mode_to_security_class(mode), - name, - &newsid); - if (rc) { - printk(KERN_WARNING - "%s: security_transition_sid failed, rc=%d\n", - __func__, -rc); - return rc; - } - } + rc = selinux_determine_inode_label(d_inode(dentry->d_parent), name, + inode_mode_to_security_class(mode), + &newsid); + if (rc) + return rc; return security_sid_to_context(newsid, (char **)ctx, ctxlen); } @@ -2752,22 +2768,12 @@ static int selinux_inode_init_security(struct inode *inode, struct inode *dir, sid = tsec->sid; newsid = tsec->create_sid; - if ((sbsec->flags & SE_SBINITIALIZED) && - (sbsec->behavior == SECURITY_FS_USE_MNTPOINT)) - newsid = sbsec->mntpoint_sid; - else if (!newsid || !(sbsec->flags & SBLABEL_MNT)) { - rc = security_transition_sid(sid, dsec->sid, - inode_mode_to_security_class(inode->i_mode), - qstr, &newsid); - if (rc) { - printk(KERN_WARNING "%s: " - "security_transition_sid failed, rc=%d (dev=%s " - "ino=%ld)\n", - __func__, - -rc, inode->i_sb->s_id, inode->i_ino); - return rc; - } - } + rc = selinux_determine_inode_label( + dir, qstr, + inode_mode_to_security_class(inode->i_mode), + &newsid); + if (rc) + return rc; /* Possibly defer initialization to selinux_complete_init. */ if (sbsec->flags & SE_SBINITIALIZED) { @@ -3228,6 +3234,46 @@ static void selinux_file_free_security(struct file *file) file_free_security(file); } +/* + * Check whether a task has the ioctl permission and cmd + * operation to an inode. + */ +int ioctl_has_perm(const struct cred *cred, struct file *file, + u32 requested, u16 cmd) +{ + struct common_audit_data ad; + struct file_security_struct *fsec = file->f_security; + struct inode *inode = file_inode(file); + struct inode_security_struct *isec = inode->i_security; + struct lsm_ioctlop_audit ioctl; + u32 ssid = cred_sid(cred); + int rc; + u8 driver = cmd >> 8; + u8 xperm = cmd & 0xff; + + ad.type = LSM_AUDIT_DATA_IOCTL_OP; + ad.u.op = &ioctl; + ad.u.op->cmd = cmd; + ad.u.op->path = file->f_path; + + if (ssid != fsec->sid) { + rc = avc_has_perm(ssid, fsec->sid, + SECCLASS_FD, + FD__USE, + &ad); + if (rc) + goto out; + } + + if (unlikely(IS_PRIVATE(inode))) + return 0; + + rc = avc_has_extended_perms(ssid, isec->sid, isec->sclass, + requested, driver, xperm, &ad); +out: + return rc; +} + static int selinux_file_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { @@ -3270,7 +3316,7 @@ static int selinux_file_ioctl(struct file *file, unsigned int cmd, * to the file's ioctl() function. */ default: - error = file_has_perm(cred, file, FILE__IOCTL); + error = ioctl_has_perm(cred, file, FILE__IOCTL, (u16) cmd); } return error; } @@ -4520,6 +4566,7 @@ static int selinux_sk_alloc_security(struct sock *sk, int family, gfp_t priority sksec->peer_sid = SECINITSID_UNLABELED; sksec->sid = SECINITSID_UNLABELED; + sksec->sclass = SECCLASS_SOCKET; selinux_netlbl_sk_security_reset(sksec); sk->sk_security = sksec; diff --git a/security/selinux/include/avc.h b/security/selinux/include/avc.h index 5973c327c54e..0999df03af8b 100644 --- a/security/selinux/include/avc.h +++ b/security/selinux/include/avc.h @@ -143,6 +143,7 @@ static inline int avc_audit(u32 ssid, u32 tsid, } #define AVC_STRICT 1 /* Ignore permissive mode. */ +#define AVC_EXTENDED_PERMS 2 /* update extended permissions */ int avc_has_perm_noaudit(u32 ssid, u32 tsid, u16 tclass, u32 requested, unsigned flags, @@ -156,6 +157,10 @@ int avc_has_perm_flags(u32 ssid, u32 tsid, struct common_audit_data *auditdata, int flags); +int avc_has_extended_perms(u32 ssid, u32 tsid, u16 tclass, u32 requested, + u8 driver, u8 perm, struct common_audit_data *ad); + + u32 avc_policy_seqno(void); #define AVC_CALLBACK_GRANT 1 @@ -166,6 +171,7 @@ u32 avc_policy_seqno(void); #define AVC_CALLBACK_AUDITALLOW_DISABLE 32 #define AVC_CALLBACK_AUDITDENY_ENABLE 64 #define AVC_CALLBACK_AUDITDENY_DISABLE 128 +#define AVC_CALLBACK_ADD_XPERMS 256 int avc_add_callback(int (*callback)(u32 event), u32 events); diff --git a/security/selinux/include/security.h b/security/selinux/include/security.h index 36993ad1c067..6a681d26bf20 100644 --- a/security/selinux/include/security.h +++ b/security/selinux/include/security.h @@ -35,13 +35,14 @@ #define POLICYDB_VERSION_NEW_OBJECT_DEFAULTS 27 #define POLICYDB_VERSION_DEFAULT_TYPE 28 #define POLICYDB_VERSION_CONSTRAINT_NAMES 29 +#define POLICYDB_VERSION_XPERMS_IOCTL 30 /* Range of policy versions we understand*/ #define POLICYDB_VERSION_MIN POLICYDB_VERSION_BASE #ifdef CONFIG_SECURITY_SELINUX_POLICYDB_VERSION_MAX #define POLICYDB_VERSION_MAX CONFIG_SECURITY_SELINUX_POLICYDB_VERSION_MAX_VALUE #else -#define POLICYDB_VERSION_MAX POLICYDB_VERSION_CONSTRAINT_NAMES +#define POLICYDB_VERSION_MAX POLICYDB_VERSION_XPERMS_IOCTL #endif /* Mask for just the mount related flags */ @@ -109,11 +110,38 @@ struct av_decision { u32 flags; }; +#define XPERMS_ALLOWED 1 +#define XPERMS_AUDITALLOW 2 +#define XPERMS_DONTAUDIT 4 + +#define security_xperm_set(perms, x) (perms[x >> 5] |= 1 << (x & 0x1f)) +#define security_xperm_test(perms, x) (1 & (perms[x >> 5] >> (x & 0x1f))) +struct extended_perms_data { + u32 p[8]; +}; + +struct extended_perms_decision { + u8 used; + u8 driver; + struct extended_perms_data *allowed; + struct extended_perms_data *auditallow; + struct extended_perms_data *dontaudit; +}; + +struct extended_perms { + u16 len; /* length associated decision chain */ + struct extended_perms_data drivers; /* flag drivers that are used */ +}; + /* definitions of av_decision.flags */ #define AVD_FLAGS_PERMISSIVE 0x0001 void security_compute_av(u32 ssid, u32 tsid, - u16 tclass, struct av_decision *avd); + u16 tclass, struct av_decision *avd, + struct extended_perms *xperms); + +void security_compute_xperms_decision(u32 ssid, u32 tsid, u16 tclass, + u8 driver, struct extended_perms_decision *xpermd); void security_compute_av_user(u32 ssid, u32 tsid, u16 tclass, struct av_decision *avd); diff --git a/security/selinux/ss/avtab.c b/security/selinux/ss/avtab.c index b64f2772b030..3628d3a868b6 100644 --- a/security/selinux/ss/avtab.c +++ b/security/selinux/ss/avtab.c @@ -24,6 +24,7 @@ #include "policydb.h" static struct kmem_cache *avtab_node_cachep; +static struct kmem_cache *avtab_xperms_cachep; /* Based on MurmurHash3, written by Austin Appleby and placed in the * public domain. @@ -70,11 +71,24 @@ avtab_insert_node(struct avtab *h, int hvalue, struct avtab_key *key, struct avtab_datum *datum) { struct avtab_node *newnode; + struct avtab_extended_perms *xperms; newnode = kmem_cache_zalloc(avtab_node_cachep, GFP_KERNEL); if (newnode == NULL) return NULL; newnode->key = *key; - newnode->datum = *datum; + + if (key->specified & AVTAB_XPERMS) { + xperms = kmem_cache_zalloc(avtab_xperms_cachep, GFP_KERNEL); + if (xperms == NULL) { + kmem_cache_free(avtab_node_cachep, newnode); + return NULL; + } + *xperms = *(datum->u.xperms); + newnode->datum.u.xperms = xperms; + } else { + newnode->datum.u.data = datum->u.data; + } + if (prev) { newnode->next = prev->next; prev->next = newnode; @@ -107,8 +121,12 @@ static int avtab_insert(struct avtab *h, struct avtab_key *key, struct avtab_dat if (key->source_type == cur->key.source_type && key->target_type == cur->key.target_type && key->target_class == cur->key.target_class && - (specified & cur->key.specified)) + (specified & cur->key.specified)) { + /* extended perms may not be unique */ + if (specified & AVTAB_XPERMS) + break; return -EEXIST; + } if (key->source_type < cur->key.source_type) break; if (key->source_type == cur->key.source_type && @@ -271,6 +289,9 @@ void avtab_destroy(struct avtab *h) while (cur) { temp = cur; cur = cur->next; + if (temp->key.specified & AVTAB_XPERMS) + kmem_cache_free(avtab_xperms_cachep, + temp->datum.u.xperms); kmem_cache_free(avtab_node_cachep, temp); } } @@ -359,7 +380,10 @@ static uint16_t spec_order[] = { AVTAB_AUDITALLOW, AVTAB_TRANSITION, AVTAB_CHANGE, - AVTAB_MEMBER + AVTAB_MEMBER, + AVTAB_XPERMS_ALLOWED, + AVTAB_XPERMS_AUDITALLOW, + AVTAB_XPERMS_DONTAUDIT }; int avtab_read_item(struct avtab *a, void *fp, struct policydb *pol, @@ -369,10 +393,11 @@ int avtab_read_item(struct avtab *a, void *fp, struct policydb *pol, { __le16 buf16[4]; u16 enabled; - __le32 buf32[7]; u32 items, items2, val, vers = pol->policyvers; struct avtab_key key; struct avtab_datum datum; + struct avtab_extended_perms xperms; + __le32 buf32[ARRAY_SIZE(xperms.perms.p)]; int i, rc; unsigned set; @@ -429,11 +454,15 @@ int avtab_read_item(struct avtab *a, void *fp, struct policydb *pol, printk(KERN_ERR "SELinux: avtab: entry has both access vectors and types\n"); return -EINVAL; } + if (val & AVTAB_XPERMS) { + printk(KERN_ERR "SELinux: avtab: entry has extended permissions\n"); + return -EINVAL; + } for (i = 0; i < ARRAY_SIZE(spec_order); i++) { if (val & spec_order[i]) { key.specified = spec_order[i] | enabled; - datum.data = le32_to_cpu(buf32[items++]); + datum.u.data = le32_to_cpu(buf32[items++]); rc = insertf(a, &key, &datum, p); if (rc) return rc; @@ -476,14 +505,42 @@ int avtab_read_item(struct avtab *a, void *fp, struct policydb *pol, return -EINVAL; } - rc = next_entry(buf32, fp, sizeof(u32)); - if (rc) { - printk(KERN_ERR "SELinux: avtab: truncated entry\n"); - return rc; + if ((vers < POLICYDB_VERSION_XPERMS_IOCTL) && + (key.specified & AVTAB_XPERMS)) { + printk(KERN_ERR "SELinux: avtab: policy version %u does not " + "support extended permissions rules and one " + "was specified\n", vers); + return -EINVAL; + } else if (key.specified & AVTAB_XPERMS) { + memset(&xperms, 0, sizeof(struct avtab_extended_perms)); + rc = next_entry(&xperms.specified, fp, sizeof(u8)); + if (rc) { + printk(KERN_ERR "SELinux: avtab: truncated entry\n"); + return rc; + } + rc = next_entry(&xperms.driver, fp, sizeof(u8)); + if (rc) { + printk(KERN_ERR "SELinux: avtab: truncated entry\n"); + return rc; + } + rc = next_entry(buf32, fp, sizeof(u32)*ARRAY_SIZE(xperms.perms.p)); + if (rc) { + printk(KERN_ERR "SELinux: avtab: truncated entry\n"); + return rc; + } + for (i = 0; i < ARRAY_SIZE(xperms.perms.p); i++) + xperms.perms.p[i] = le32_to_cpu(buf32[i]); + datum.u.xperms = &xperms; + } else { + rc = next_entry(buf32, fp, sizeof(u32)); + if (rc) { + printk(KERN_ERR "SELinux: avtab: truncated entry\n"); + return rc; + } + datum.u.data = le32_to_cpu(*buf32); } - datum.data = le32_to_cpu(*buf32); if ((key.specified & AVTAB_TYPE) && - !policydb_type_isvalid(pol, datum.data)) { + !policydb_type_isvalid(pol, datum.u.data)) { printk(KERN_ERR "SELinux: avtab: invalid type\n"); return -EINVAL; } @@ -543,8 +600,9 @@ bad: int avtab_write_item(struct policydb *p, struct avtab_node *cur, void *fp) { __le16 buf16[4]; - __le32 buf32[1]; + __le32 buf32[ARRAY_SIZE(cur->datum.u.xperms->perms.p)]; int rc; + unsigned int i; buf16[0] = cpu_to_le16(cur->key.source_type); buf16[1] = cpu_to_le16(cur->key.target_type); @@ -553,8 +611,22 @@ int avtab_write_item(struct policydb *p, struct avtab_node *cur, void *fp) rc = put_entry(buf16, sizeof(u16), 4, fp); if (rc) return rc; - buf32[0] = cpu_to_le32(cur->datum.data); - rc = put_entry(buf32, sizeof(u32), 1, fp); + + if (cur->key.specified & AVTAB_XPERMS) { + rc = put_entry(&cur->datum.u.xperms->specified, sizeof(u8), 1, fp); + if (rc) + return rc; + rc = put_entry(&cur->datum.u.xperms->driver, sizeof(u8), 1, fp); + if (rc) + return rc; + for (i = 0; i < ARRAY_SIZE(cur->datum.u.xperms->perms.p); i++) + buf32[i] = cpu_to_le32(cur->datum.u.xperms->perms.p[i]); + rc = put_entry(buf32, sizeof(u32), + ARRAY_SIZE(cur->datum.u.xperms->perms.p), fp); + } else { + buf32[0] = cpu_to_le32(cur->datum.u.data); + rc = put_entry(buf32, sizeof(u32), 1, fp); + } if (rc) return rc; return 0; @@ -588,9 +660,13 @@ void avtab_cache_init(void) avtab_node_cachep = kmem_cache_create("avtab_node", sizeof(struct avtab_node), 0, SLAB_PANIC, NULL); + avtab_xperms_cachep = kmem_cache_create("avtab_extended_perms", + sizeof(struct avtab_extended_perms), + 0, SLAB_PANIC, NULL); } void avtab_cache_destroy(void) { kmem_cache_destroy(avtab_node_cachep); + kmem_cache_destroy(avtab_xperms_cachep); } diff --git a/security/selinux/ss/avtab.h b/security/selinux/ss/avtab.h index adb451cd44f9..d946c9dc3c9c 100644 --- a/security/selinux/ss/avtab.h +++ b/security/selinux/ss/avtab.h @@ -23,6 +23,7 @@ #ifndef _SS_AVTAB_H_ #define _SS_AVTAB_H_ +#include "security.h" #include <linux/flex_array.h> struct avtab_key { @@ -37,13 +38,43 @@ struct avtab_key { #define AVTAB_MEMBER 0x0020 #define AVTAB_CHANGE 0x0040 #define AVTAB_TYPE (AVTAB_TRANSITION | AVTAB_MEMBER | AVTAB_CHANGE) +/* extended permissions */ +#define AVTAB_XPERMS_ALLOWED 0x0100 +#define AVTAB_XPERMS_AUDITALLOW 0x0200 +#define AVTAB_XPERMS_DONTAUDIT 0x0400 +#define AVTAB_XPERMS (AVTAB_XPERMS_ALLOWED | \ + AVTAB_XPERMS_AUDITALLOW | \ + AVTAB_XPERMS_DONTAUDIT) #define AVTAB_ENABLED_OLD 0x80000000 /* reserved for used in cond_avtab */ #define AVTAB_ENABLED 0x8000 /* reserved for used in cond_avtab */ u16 specified; /* what field is specified */ }; +/* + * For operations that require more than the 32 permissions provided by the avc + * extended permissions may be used to provide 256 bits of permissions. + */ +struct avtab_extended_perms { +/* These are not flags. All 256 values may be used */ +#define AVTAB_XPERMS_IOCTLFUNCTION 0x01 +#define AVTAB_XPERMS_IOCTLDRIVER 0x02 + /* extension of the avtab_key specified */ + u8 specified; /* ioctl, netfilter, ... */ + /* + * if 256 bits is not adequate as is often the case with ioctls, then + * multiple extended perms may be used and the driver field + * specifies which permissions are included. + */ + u8 driver; + /* 256 bits of permissions */ + struct extended_perms_data perms; +}; + struct avtab_datum { - u32 data; /* access vector or type value */ + union { + u32 data; /* access vector or type value */ + struct avtab_extended_perms *xperms; + } u; }; struct avtab_node { diff --git a/security/selinux/ss/conditional.c b/security/selinux/ss/conditional.c index 62c6773be0b7..18643bf9894d 100644 --- a/security/selinux/ss/conditional.c +++ b/security/selinux/ss/conditional.c @@ -15,6 +15,7 @@ #include "security.h" #include "conditional.h" +#include "services.h" /* * cond_evaluate_expr evaluates a conditional expr @@ -612,21 +613,39 @@ int cond_write_list(struct policydb *p, struct cond_node *list, void *fp) return 0; } + +void cond_compute_xperms(struct avtab *ctab, struct avtab_key *key, + struct extended_perms_decision *xpermd) +{ + struct avtab_node *node; + + if (!ctab || !key || !xpermd) + return; + + for (node = avtab_search_node(ctab, key); node; + node = avtab_search_node_next(node, key->specified)) { + if (node->key.specified & AVTAB_ENABLED) + services_compute_xperms_decision(xpermd, node); + } + return; + +} /* Determine whether additional permissions are granted by the conditional * av table, and if so, add them to the result */ -void cond_compute_av(struct avtab *ctab, struct avtab_key *key, struct av_decision *avd) +void cond_compute_av(struct avtab *ctab, struct avtab_key *key, + struct av_decision *avd, struct extended_perms *xperms) { struct avtab_node *node; - if (!ctab || !key || !avd) + if (!ctab || !key || !avd || !xperms) return; for (node = avtab_search_node(ctab, key); node; node = avtab_search_node_next(node, key->specified)) { if ((u16)(AVTAB_ALLOWED|AVTAB_ENABLED) == (node->key.specified & (AVTAB_ALLOWED|AVTAB_ENABLED))) - avd->allowed |= node->datum.data; + avd->allowed |= node->datum.u.data; if ((u16)(AVTAB_AUDITDENY|AVTAB_ENABLED) == (node->key.specified & (AVTAB_AUDITDENY|AVTAB_ENABLED))) /* Since a '0' in an auditdeny mask represents a @@ -634,10 +653,13 @@ void cond_compute_av(struct avtab *ctab, struct avtab_key *key, struct av_decisi * the '&' operand to ensure that all '0's in the mask * are retained (much unlike the allow and auditallow cases). */ - avd->auditdeny &= node->datum.data; + avd->auditdeny &= node->datum.u.data; if ((u16)(AVTAB_AUDITALLOW|AVTAB_ENABLED) == (node->key.specified & (AVTAB_AUDITALLOW|AVTAB_ENABLED))) - avd->auditallow |= node->datum.data; + avd->auditallow |= node->datum.u.data; + if ((node->key.specified & AVTAB_ENABLED) && + (node->key.specified & AVTAB_XPERMS)) + services_compute_xperms_drivers(xperms, node); } return; } diff --git a/security/selinux/ss/conditional.h b/security/selinux/ss/conditional.h index 4d1f87466508..ddb43e7e1c75 100644 --- a/security/selinux/ss/conditional.h +++ b/security/selinux/ss/conditional.h @@ -73,8 +73,10 @@ int cond_read_list(struct policydb *p, void *fp); int cond_write_bool(void *key, void *datum, void *ptr); int cond_write_list(struct policydb *p, struct cond_node *list, void *fp); -void cond_compute_av(struct avtab *ctab, struct avtab_key *key, struct av_decision *avd); - +void cond_compute_av(struct avtab *ctab, struct avtab_key *key, + struct av_decision *avd, struct extended_perms *xperms); +void cond_compute_xperms(struct avtab *ctab, struct avtab_key *key, + struct extended_perms_decision *xpermd); int evaluate_cond_node(struct policydb *p, struct cond_node *node); #endif /* _CONDITIONAL_H_ */ diff --git a/security/selinux/ss/policydb.c b/security/selinux/ss/policydb.c index 74aa224267c1..992a31530825 100644 --- a/security/selinux/ss/policydb.c +++ b/security/selinux/ss/policydb.c @@ -148,6 +148,11 @@ static struct policydb_compat_info policydb_compat[] = { .sym_num = SYM_NUM, .ocon_num = OCON_NUM, }, + { + .version = POLICYDB_VERSION_XPERMS_IOCTL, + .sym_num = SYM_NUM, + .ocon_num = OCON_NUM, + }, }; static struct policydb_compat_info *policydb_lookup_compat(int version) diff --git a/security/selinux/ss/services.c b/security/selinux/ss/services.c index 9e2d82070915..b7df12ba61d8 100644 --- a/security/selinux/ss/services.c +++ b/security/selinux/ss/services.c @@ -93,9 +93,10 @@ static int context_struct_to_string(struct context *context, char **scontext, u32 *scontext_len); static void context_struct_compute_av(struct context *scontext, - struct context *tcontext, - u16 tclass, - struct av_decision *avd); + struct context *tcontext, + u16 tclass, + struct av_decision *avd, + struct extended_perms *xperms); struct selinux_mapping { u16 value; /* policy value */ @@ -565,7 +566,8 @@ static void type_attribute_bounds_av(struct context *scontext, context_struct_compute_av(&lo_scontext, tcontext, tclass, - &lo_avd); + &lo_avd, + NULL); if ((lo_avd.allowed & avd->allowed) == avd->allowed) return; /* no masked permission */ masked = ~lo_avd.allowed & avd->allowed; @@ -580,7 +582,8 @@ static void type_attribute_bounds_av(struct context *scontext, context_struct_compute_av(scontext, &lo_tcontext, tclass, - &lo_avd); + &lo_avd, + NULL); if ((lo_avd.allowed & avd->allowed) == avd->allowed) return; /* no masked permission */ masked = ~lo_avd.allowed & avd->allowed; @@ -596,7 +599,8 @@ static void type_attribute_bounds_av(struct context *scontext, context_struct_compute_av(&lo_scontext, &lo_tcontext, tclass, - &lo_avd); + &lo_avd, + NULL); if ((lo_avd.allowed & avd->allowed) == avd->allowed) return; /* no masked permission */ masked = ~lo_avd.allowed & avd->allowed; @@ -613,13 +617,39 @@ static void type_attribute_bounds_av(struct context *scontext, } /* - * Compute access vectors based on a context structure pair for - * the permissions in a particular class. + * flag which drivers have permissions + * only looking for ioctl based extended permssions + */ +void services_compute_xperms_drivers( + struct extended_perms *xperms, + struct avtab_node *node) +{ + unsigned int i; + + if (node->datum.u.xperms->specified == AVTAB_XPERMS_IOCTLDRIVER) { + /* if one or more driver has all permissions allowed */ + for (i = 0; i < ARRAY_SIZE(xperms->drivers.p); i++) + xperms->drivers.p[i] |= node->datum.u.xperms->perms.p[i]; + } else if (node->datum.u.xperms->specified == AVTAB_XPERMS_IOCTLFUNCTION) { + /* if allowing permissions within a driver */ + security_xperm_set(xperms->drivers.p, + node->datum.u.xperms->driver); + } + + /* If no ioctl commands are allowed, ignore auditallow and auditdeny */ + if (node->key.specified & AVTAB_XPERMS_ALLOWED) + xperms->len = 1; +} + +/* + * Compute access vectors and extended permissions based on a context + * structure pair for the permissions in a particular class. */ static void context_struct_compute_av(struct context *scontext, - struct context *tcontext, - u16 tclass, - struct av_decision *avd) + struct context *tcontext, + u16 tclass, + struct av_decision *avd, + struct extended_perms *xperms) { struct constraint_node *constraint; struct role_allow *ra; @@ -633,6 +663,10 @@ static void context_struct_compute_av(struct context *scontext, avd->allowed = 0; avd->auditallow = 0; avd->auditdeny = 0xffffffff; + if (xperms) { + memset(&xperms->drivers, 0, sizeof(xperms->drivers)); + xperms->len = 0; + } if (unlikely(!tclass || tclass > policydb.p_classes.nprim)) { if (printk_ratelimit()) @@ -647,7 +681,7 @@ static void context_struct_compute_av(struct context *scontext, * this permission check, then use it. */ avkey.target_class = tclass; - avkey.specified = AVTAB_AV; + avkey.specified = AVTAB_AV | AVTAB_XPERMS; sattr = flex_array_get(policydb.type_attr_map_array, scontext->type - 1); BUG_ON(!sattr); tattr = flex_array_get(policydb.type_attr_map_array, tcontext->type - 1); @@ -660,15 +694,18 @@ static void context_struct_compute_av(struct context *scontext, node; node = avtab_search_node_next(node, avkey.specified)) { if (node->key.specified == AVTAB_ALLOWED) - avd->allowed |= node->datum.data; + avd->allowed |= node->datum.u.data; else if (node->key.specified == AVTAB_AUDITALLOW) - avd->auditallow |= node->datum.data; + avd->auditallow |= node->datum.u.data; else if (node->key.specified == AVTAB_AUDITDENY) - avd->auditdeny &= node->datum.data; + avd->auditdeny &= node->datum.u.data; + else if (xperms && (node->key.specified & AVTAB_XPERMS)) + services_compute_xperms_drivers(xperms, node); } /* Check conditional av table for additional permissions */ - cond_compute_av(&policydb.te_cond_avtab, &avkey, avd); + cond_compute_av(&policydb.te_cond_avtab, &avkey, + avd, xperms); } } @@ -899,6 +936,139 @@ static void avd_init(struct av_decision *avd) avd->flags = 0; } +void services_compute_xperms_decision(struct extended_perms_decision *xpermd, + struct avtab_node *node) +{ + unsigned int i; + + if (node->datum.u.xperms->specified == AVTAB_XPERMS_IOCTLFUNCTION) { + if (xpermd->driver != node->datum.u.xperms->driver) + return; + } else if (node->datum.u.xperms->specified == AVTAB_XPERMS_IOCTLDRIVER) { + if (!security_xperm_test(node->datum.u.xperms->perms.p, + xpermd->driver)) + return; + } else { + BUG(); + } + + if (node->key.specified == AVTAB_XPERMS_ALLOWED) { + xpermd->used |= XPERMS_ALLOWED; + if (node->datum.u.xperms->specified == AVTAB_XPERMS_IOCTLDRIVER) { + memset(xpermd->allowed->p, 0xff, + sizeof(xpermd->allowed->p)); + } + if (node->datum.u.xperms->specified == AVTAB_XPERMS_IOCTLFUNCTION) { + for (i = 0; i < ARRAY_SIZE(xpermd->allowed->p); i++) + xpermd->allowed->p[i] |= + node->datum.u.xperms->perms.p[i]; + } + } else if (node->key.specified == AVTAB_XPERMS_AUDITALLOW) { + xpermd->used |= XPERMS_AUDITALLOW; + if (node->datum.u.xperms->specified == AVTAB_XPERMS_IOCTLDRIVER) { + memset(xpermd->auditallow->p, 0xff, + sizeof(xpermd->auditallow->p)); + } + if (node->datum.u.xperms->specified == AVTAB_XPERMS_IOCTLFUNCTION) { + for (i = 0; i < ARRAY_SIZE(xpermd->auditallow->p); i++) + xpermd->auditallow->p[i] |= + node->datum.u.xperms->perms.p[i]; + } + } else if (node->key.specified == AVTAB_XPERMS_DONTAUDIT) { + xpermd->used |= XPERMS_DONTAUDIT; + if (node->datum.u.xperms->specified == AVTAB_XPERMS_IOCTLDRIVER) { + memset(xpermd->dontaudit->p, 0xff, + sizeof(xpermd->dontaudit->p)); + } + if (node->datum.u.xperms->specified == AVTAB_XPERMS_IOCTLFUNCTION) { + for (i = 0; i < ARRAY_SIZE(xpermd->dontaudit->p); i++) + xpermd->dontaudit->p[i] |= + node->datum.u.xperms->perms.p[i]; + } + } else { + BUG(); + } +} + +void security_compute_xperms_decision(u32 ssid, + u32 tsid, + u16 orig_tclass, + u8 driver, + struct extended_perms_decision *xpermd) +{ + u16 tclass; + struct context *scontext, *tcontext; + struct avtab_key avkey; + struct avtab_node *node; + struct ebitmap *sattr, *tattr; + struct ebitmap_node *snode, *tnode; + unsigned int i, j; + + xpermd->driver = driver; + xpermd->used = 0; + memset(xpermd->allowed->p, 0, sizeof(xpermd->allowed->p)); + memset(xpermd->auditallow->p, 0, sizeof(xpermd->auditallow->p)); + memset(xpermd->dontaudit->p, 0, sizeof(xpermd->dontaudit->p)); + + read_lock(&policy_rwlock); + if (!ss_initialized) + goto allow; + + scontext = sidtab_search(&sidtab, ssid); + if (!scontext) { + printk(KERN_ERR "SELinux: %s: unrecognized SID %d\n", + __func__, ssid); + goto out; + } + + tcontext = sidtab_search(&sidtab, tsid); + if (!tcontext) { + printk(KERN_ERR "SELinux: %s: unrecognized SID %d\n", + __func__, tsid); + goto out; + } + + tclass = unmap_class(orig_tclass); + if (unlikely(orig_tclass && !tclass)) { + if (policydb.allow_unknown) + goto allow; + goto out; + } + + + if (unlikely(!tclass || tclass > policydb.p_classes.nprim)) { + pr_warn_ratelimited("SELinux: Invalid class %hu\n", tclass); + goto out; + } + + avkey.target_class = tclass; + avkey.specified = AVTAB_XPERMS; + sattr = flex_array_get(policydb.type_attr_map_array, + scontext->type - 1); + BUG_ON(!sattr); + tattr = flex_array_get(policydb.type_attr_map_array, + tcontext->type - 1); + BUG_ON(!tattr); + ebitmap_for_each_positive_bit(sattr, snode, i) { + ebitmap_for_each_positive_bit(tattr, tnode, j) { + avkey.source_type = i + 1; + avkey.target_type = j + 1; + for (node = avtab_search_node(&policydb.te_avtab, &avkey); + node; + node = avtab_search_node_next(node, avkey.specified)) + services_compute_xperms_decision(xpermd, node); + + cond_compute_xperms(&policydb.te_cond_avtab, + &avkey, xpermd); + } + } +out: + read_unlock(&policy_rwlock); + return; +allow: + memset(xpermd->allowed->p, 0xff, sizeof(xpermd->allowed->p)); + goto out; +} /** * security_compute_av - Compute access vector decisions. @@ -906,6 +1076,7 @@ static void avd_init(struct av_decision *avd) * @tsid: target security identifier * @tclass: target security class * @avd: access vector decisions + * @xperms: extended permissions * * Compute a set of access vector decisions based on the * SID pair (@ssid, @tsid) for the permissions in @tclass. @@ -913,13 +1084,15 @@ static void avd_init(struct av_decision *avd) void security_compute_av(u32 ssid, u32 tsid, u16 orig_tclass, - struct av_decision *avd) + struct av_decision *avd, + struct extended_perms *xperms) { u16 tclass; struct context *scontext = NULL, *tcontext = NULL; read_lock(&policy_rwlock); avd_init(avd); + xperms->len = 0; if (!ss_initialized) goto allow; @@ -947,7 +1120,7 @@ void security_compute_av(u32 ssid, goto allow; goto out; } - context_struct_compute_av(scontext, tcontext, tclass, avd); + context_struct_compute_av(scontext, tcontext, tclass, avd, xperms); map_decision(orig_tclass, avd, policydb.allow_unknown); out: read_unlock(&policy_rwlock); @@ -993,7 +1166,7 @@ void security_compute_av_user(u32 ssid, goto out; } - context_struct_compute_av(scontext, tcontext, tclass, avd); + context_struct_compute_av(scontext, tcontext, tclass, avd, NULL); out: read_unlock(&policy_rwlock); return; @@ -1515,7 +1688,7 @@ static int security_compute_sid(u32 ssid, if (avdatum) { /* Use the type from the type transition/member/change rule. */ - newcontext.type = avdatum->data; + newcontext.type = avdatum->u.data; } /* if we have a objname this is a file trans check so check those rules */ diff --git a/security/selinux/ss/services.h b/security/selinux/ss/services.h index e8d907e903cd..6abcd8729ec3 100644 --- a/security/selinux/ss/services.h +++ b/security/selinux/ss/services.h @@ -11,5 +11,11 @@ extern struct policydb policydb; +void services_compute_xperms_drivers(struct extended_perms *xperms, + struct avtab_node *node); + +void services_compute_xperms_decision(struct extended_perms_decision *xpermd, + struct avtab_node *node); + #endif /* _SS_SERVICES_H_ */ diff --git a/security/smack/smack.h b/security/smack/smack.h index 244e035e5a99..fff0c612bbb7 100644 --- a/security/smack/smack.h +++ b/security/smack/smack.h @@ -17,12 +17,27 @@ #include <linux/spinlock.h> #include <linux/lsm_hooks.h> #include <linux/in.h> +#if IS_ENABLED(CONFIG_IPV6) +#include <linux/in6.h> +#endif /* CONFIG_IPV6 */ #include <net/netlabel.h> #include <linux/list.h> #include <linux/rculist.h> #include <linux/lsm_audit.h> /* + * Use IPv6 port labeling if IPv6 is enabled and secmarks + * are not being used. + */ +#if IS_ENABLED(CONFIG_IPV6) && !defined(CONFIG_SECURITY_SMACK_NETFILTER) +#define SMACK_IPV6_PORT_LABELING 1 +#endif + +#if IS_ENABLED(CONFIG_IPV6) && defined(CONFIG_SECURITY_SMACK_NETFILTER) +#define SMACK_IPV6_SECMARK_LABELING 1 +#endif + +/* * Smack labels were limited to 23 characters for a long time. */ #define SMK_LABELLEN 24 @@ -118,15 +133,30 @@ struct smack_rule { }; /* - * An entry in the table identifying hosts. + * An entry in the table identifying IPv4 hosts. */ -struct smk_netlbladdr { +struct smk_net4addr { struct list_head list; - struct sockaddr_in smk_host; /* network address */ + struct in_addr smk_host; /* network address */ struct in_addr smk_mask; /* network mask */ + int smk_masks; /* mask size */ + struct smack_known *smk_label; /* label */ +}; + +#if IS_ENABLED(CONFIG_IPV6) +/* + * An entry in the table identifying IPv6 hosts. + */ +struct smk_net6addr { + struct list_head list; + struct in6_addr smk_host; /* network address */ + struct in6_addr smk_mask; /* network mask */ + int smk_masks; /* mask size */ struct smack_known *smk_label; /* label */ }; +#endif /* CONFIG_IPV6 */ +#ifdef SMACK_IPV6_PORT_LABELING /* * An entry in the table identifying ports. */ @@ -137,12 +167,31 @@ struct smk_port_label { struct smack_known *smk_in; /* inbound label */ struct smack_known *smk_out; /* outgoing label */ }; +#endif /* SMACK_IPV6_PORT_LABELING */ struct smack_onlycap { struct list_head list; struct smack_known *smk_label; }; +/* Super block security struct flags for mount options */ +#define FSDEFAULT_MNT 0x01 +#define FSFLOOR_MNT 0x02 +#define FSHAT_MNT 0x04 +#define FSROOT_MNT 0x08 +#define FSTRANS_MNT 0x10 + +#define NUM_SMK_MNT_OPTS 5 + +enum { + Opt_error = -1, + Opt_fsdefault = 1, + Opt_fsfloor = 2, + Opt_fshat = 3, + Opt_fsroot = 4, + Opt_fstransmute = 5, +}; + /* * Mount options */ @@ -152,6 +201,7 @@ struct smack_onlycap { #define SMK_FSROOT "smackfsroot=" #define SMK_FSTRANS "smackfstransmute=" +#define SMACK_DELETE_OPTION "-DELETE" #define SMACK_CIPSO_OPTION "-CIPSO" /* @@ -234,10 +284,6 @@ struct smk_audit_info { struct smack_audit_data sad; #endif }; -/* - * These functions are in smack_lsm.c - */ -struct inode_smack *new_inode_smack(struct smack_known *); /* * These functions are in smack_access.c @@ -267,7 +313,6 @@ extern struct smack_known *smack_syslog_label; #ifdef CONFIG_SECURITY_SMACK_BRINGUP extern struct smack_known *smack_unconfined; #endif -extern struct smack_known smack_cipso_option; extern int smack_ptrace_rule; extern struct smack_known smack_known_floor; @@ -279,7 +324,10 @@ extern struct smack_known smack_known_web; extern struct mutex smack_known_lock; extern struct list_head smack_known_list; -extern struct list_head smk_netlbladdr_list; +extern struct list_head smk_net4addr_list; +#if IS_ENABLED(CONFIG_IPV6) +extern struct list_head smk_net6addr_list; +#endif /* CONFIG_IPV6 */ extern struct mutex smack_onlycap_lock; extern struct list_head smack_onlycap_list; diff --git a/security/smack/smack_access.c b/security/smack/smack_access.c index 00f6b38bffbd..bc1053fb5d1d 100644 --- a/security/smack/smack_access.c +++ b/security/smack/smack_access.c @@ -639,6 +639,12 @@ int smack_privileged(int cap) struct smack_known *skp = smk_of_current(); struct smack_onlycap *sop; + /* + * All kernel tasks are privileged + */ + if (unlikely(current->flags & PF_KTHREAD)) + return 1; + if (!capable(cap)) return 0; diff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c index a143328f75eb..996c88956438 100644 --- a/security/smack/smack_lsm.c +++ b/security/smack/smack_lsm.c @@ -41,6 +41,7 @@ #include <linux/msg.h> #include <linux/shm.h> #include <linux/binfmts.h> +#include <linux/parser.h> #include "smack.h" #define TRANS_TRUE "TRUE" @@ -50,12 +51,21 @@ #define SMK_RECEIVING 1 #define SMK_SENDING 2 -#if IS_ENABLED(CONFIG_IPV6) && !defined(CONFIG_SECURITY_SMACK_NETFILTER) +#ifdef SMACK_IPV6_PORT_LABELING LIST_HEAD(smk_ipv6_port_list); -#endif /* CONFIG_IPV6 && !CONFIG_SECURITY_SMACK_NETFILTER */ +#endif static struct kmem_cache *smack_inode_cache; int smack_enabled; +static const match_table_t smk_mount_tokens = { + {Opt_fsdefault, SMK_FSDEFAULT "%s"}, + {Opt_fsfloor, SMK_FSFLOOR "%s"}, + {Opt_fshat, SMK_FSHAT "%s"}, + {Opt_fsroot, SMK_FSROOT "%s"}, + {Opt_fstransmute, SMK_FSTRANS "%s"}, + {Opt_error, NULL}, +}; + #ifdef CONFIG_SECURITY_SMACK_BRINGUP static char *smk_bu_mess[] = { "Bringup Error", /* Unused */ @@ -281,7 +291,7 @@ static struct smack_known *smk_fetch(const char *name, struct inode *ip, * * Returns the new blob or NULL if there's no memory available */ -struct inode_smack *new_inode_smack(struct smack_known *skp) +static struct inode_smack *new_inode_smack(struct smack_known *skp) { struct inode_smack *isp; @@ -577,76 +587,197 @@ static int smack_sb_copy_data(char *orig, char *smackopts) } /** - * smack_sb_kern_mount - Smack specific mount processing + * smack_parse_opts_str - parse Smack specific mount options + * @options: mount options string + * @opts: where to store converted mount opts + * + * Returns 0 on success or -ENOMEM on error. + * + * converts Smack specific mount options to generic security option format + */ +static int smack_parse_opts_str(char *options, + struct security_mnt_opts *opts) +{ + char *p; + char *fsdefault = NULL; + char *fsfloor = NULL; + char *fshat = NULL; + char *fsroot = NULL; + char *fstransmute = NULL; + int rc = -ENOMEM; + int num_mnt_opts = 0; + int token; + + opts->num_mnt_opts = 0; + + if (!options) + return 0; + + while ((p = strsep(&options, ",")) != NULL) { + substring_t args[MAX_OPT_ARGS]; + + if (!*p) + continue; + + token = match_token(p, smk_mount_tokens, args); + + switch (token) { + case Opt_fsdefault: + if (fsdefault) + goto out_opt_err; + fsdefault = match_strdup(&args[0]); + if (!fsdefault) + goto out_err; + break; + case Opt_fsfloor: + if (fsfloor) + goto out_opt_err; + fsfloor = match_strdup(&args[0]); + if (!fsfloor) + goto out_err; + break; + case Opt_fshat: + if (fshat) + goto out_opt_err; + fshat = match_strdup(&args[0]); + if (!fshat) + goto out_err; + break; + case Opt_fsroot: + if (fsroot) + goto out_opt_err; + fsroot = match_strdup(&args[0]); + if (!fsroot) + goto out_err; + break; + case Opt_fstransmute: + if (fstransmute) + goto out_opt_err; + fstransmute = match_strdup(&args[0]); + if (!fstransmute) + goto out_err; + break; + default: + rc = -EINVAL; + pr_warn("Smack: unknown mount option\n"); + goto out_err; + } + } + + opts->mnt_opts = kcalloc(NUM_SMK_MNT_OPTS, sizeof(char *), GFP_ATOMIC); + if (!opts->mnt_opts) + goto out_err; + + opts->mnt_opts_flags = kcalloc(NUM_SMK_MNT_OPTS, sizeof(int), + GFP_ATOMIC); + if (!opts->mnt_opts_flags) { + kfree(opts->mnt_opts); + goto out_err; + } + + if (fsdefault) { + opts->mnt_opts[num_mnt_opts] = fsdefault; + opts->mnt_opts_flags[num_mnt_opts++] = FSDEFAULT_MNT; + } + if (fsfloor) { + opts->mnt_opts[num_mnt_opts] = fsfloor; + opts->mnt_opts_flags[num_mnt_opts++] = FSFLOOR_MNT; + } + if (fshat) { + opts->mnt_opts[num_mnt_opts] = fshat; + opts->mnt_opts_flags[num_mnt_opts++] = FSHAT_MNT; + } + if (fsroot) { + opts->mnt_opts[num_mnt_opts] = fsroot; + opts->mnt_opts_flags[num_mnt_opts++] = FSROOT_MNT; + } + if (fstransmute) { + opts->mnt_opts[num_mnt_opts] = fstransmute; + opts->mnt_opts_flags[num_mnt_opts++] = FSTRANS_MNT; + } + + opts->num_mnt_opts = num_mnt_opts; + return 0; + +out_opt_err: + rc = -EINVAL; + pr_warn("Smack: duplicate mount options\n"); + +out_err: + kfree(fsdefault); + kfree(fsfloor); + kfree(fshat); + kfree(fsroot); + kfree(fstransmute); + return rc; +} + +/** + * smack_set_mnt_opts - set Smack specific mount options * @sb: the file system superblock - * @flags: the mount flags - * @data: the smack mount options + * @opts: Smack mount options + * @kern_flags: mount option from kernel space or user space + * @set_kern_flags: where to store converted mount opts * * Returns 0 on success, an error code on failure + * + * Allow filesystems with binary mount data to explicitly set Smack mount + * labels. */ -static int smack_sb_kern_mount(struct super_block *sb, int flags, void *data) +static int smack_set_mnt_opts(struct super_block *sb, + struct security_mnt_opts *opts, + unsigned long kern_flags, + unsigned long *set_kern_flags) { struct dentry *root = sb->s_root; struct inode *inode = d_backing_inode(root); struct superblock_smack *sp = sb->s_security; struct inode_smack *isp; struct smack_known *skp; - char *op; - char *commap; + int i; + int num_opts = opts->num_mnt_opts; int transmute = 0; - int specified = 0; if (sp->smk_initialized) return 0; sp->smk_initialized = 1; - for (op = data; op != NULL; op = commap) { - commap = strchr(op, ','); - if (commap != NULL) - *commap++ = '\0'; - - if (strncmp(op, SMK_FSHAT, strlen(SMK_FSHAT)) == 0) { - op += strlen(SMK_FSHAT); - skp = smk_import_entry(op, 0); + for (i = 0; i < num_opts; i++) { + switch (opts->mnt_opts_flags[i]) { + case FSDEFAULT_MNT: + skp = smk_import_entry(opts->mnt_opts[i], 0); 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); + sp->smk_default = skp; + break; + case FSFLOOR_MNT: + skp = smk_import_entry(opts->mnt_opts[i], 0); 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); + break; + case FSHAT_MNT: + skp = smk_import_entry(opts->mnt_opts[i], 0); 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); + sp->smk_hat = skp; + break; + case FSROOT_MNT: + skp = smk_import_entry(opts->mnt_opts[i], 0); 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); + break; + case FSTRANS_MNT: + skp = smk_import_entry(opts->mnt_opts[i], 0); if (IS_ERR(skp)) return PTR_ERR(skp); sp->smk_root = skp; transmute = 1; - specified = 1; + break; + default: + break; } } @@ -654,7 +785,7 @@ static int smack_sb_kern_mount(struct super_block *sb, int flags, void *data) /* * Unprivileged mounts don't get to specify Smack values. */ - if (specified) + if (num_opts) return -EPERM; /* * Unprivileged mounts get root and default from the caller. @@ -663,6 +794,7 @@ static int smack_sb_kern_mount(struct super_block *sb, int flags, void *data) sp->smk_root = skp; sp->smk_default = skp; } + /* * Initialize the root inode. */ @@ -682,6 +814,37 @@ static int smack_sb_kern_mount(struct super_block *sb, int flags, void *data) } /** + * smack_sb_kern_mount - Smack specific mount processing + * @sb: the file system superblock + * @flags: the mount flags + * @data: the smack mount options + * + * Returns 0 on success, an error code on failure + */ +static int smack_sb_kern_mount(struct super_block *sb, int flags, void *data) +{ + int rc = 0; + char *options = data; + struct security_mnt_opts opts; + + security_init_mnt_opts(&opts); + + if (!options) + goto out; + + rc = smack_parse_opts_str(options, &opts); + if (rc) + goto out_err; + +out: + rc = smack_set_mnt_opts(sb, &opts, 0, NULL); + +out_err: + security_free_mnt_opts(&opts); + return rc; +} + +/** * smack_sb_statfs - Smack check on statfs * @dentry: identifies the file system in question * @@ -2113,7 +2276,7 @@ static void smack_sk_free_security(struct sock *sk) } /** -* smack_host_label - check host based restrictions +* smack_ipv4host_label - check host based restrictions * @sip: the object end * * looks for host based access restrictions @@ -2124,30 +2287,96 @@ static void smack_sk_free_security(struct sock *sk) * * Returns the label of the far end or NULL if it's not special. */ -static struct smack_known *smack_host_label(struct sockaddr_in *sip) +static struct smack_known *smack_ipv4host_label(struct sockaddr_in *sip) { - struct smk_netlbladdr *snp; + struct smk_net4addr *snp; struct in_addr *siap = &sip->sin_addr; if (siap->s_addr == 0) return NULL; - list_for_each_entry_rcu(snp, &smk_netlbladdr_list, list) + list_for_each_entry_rcu(snp, &smk_net4addr_list, list) + /* + * we break after finding the first match because + * the list is sorted from longest to shortest mask + * so we have found the most specific match + */ + if (snp->smk_host.s_addr == + (siap->s_addr & snp->smk_mask.s_addr)) + return snp->smk_label; + + return NULL; +} + +#if IS_ENABLED(CONFIG_IPV6) +/* + * smk_ipv6_localhost - Check for local ipv6 host address + * @sip: the address + * + * Returns boolean true if this is the localhost address + */ +static bool smk_ipv6_localhost(struct sockaddr_in6 *sip) +{ + __be16 *be16p = (__be16 *)&sip->sin6_addr; + __be32 *be32p = (__be32 *)&sip->sin6_addr; + + if (be32p[0] == 0 && be32p[1] == 0 && be32p[2] == 0 && be16p[6] == 0 && + ntohs(be16p[7]) == 1) + return true; + return false; +} + +/** +* smack_ipv6host_label - check host based restrictions +* @sip: the object end +* +* looks for host based access restrictions +* +* This version will only be appropriate for really small sets of single label +* hosts. The caller is responsible for ensuring that the RCU read lock is +* taken before calling this function. +* +* Returns the label of the far end or NULL if it's not special. +*/ +static struct smack_known *smack_ipv6host_label(struct sockaddr_in6 *sip) +{ + struct smk_net6addr *snp; + struct in6_addr *sap = &sip->sin6_addr; + int i; + int found = 0; + + /* + * It's local. Don't look for a host label. + */ + if (smk_ipv6_localhost(sip)) + return NULL; + + list_for_each_entry_rcu(snp, &smk_net6addr_list, list) { /* * we break after finding the first match because * the list is sorted from longest to shortest mask * so we have found the most specific match */ - if ((&snp->smk_host.sin_addr)->s_addr == - (siap->s_addr & (&snp->smk_mask)->s_addr)) { - /* we have found the special CIPSO option */ - if (snp->smk_label == &smack_cipso_option) - return NULL; - return snp->smk_label; + for (found = 1, i = 0; i < 8; i++) { + /* + * If the label is NULL the entry has + * been renounced. Ignore it. + */ + if (snp->smk_label == NULL) + continue; + if ((sap->s6_addr16[i] & snp->smk_mask.s6_addr16[i]) != + snp->smk_host.s6_addr16[i]) { + found = 0; + break; + } } + if (found) + return snp->smk_label; + } return NULL; } +#endif /* CONFIG_IPV6 */ /** * smack_netlabel - Set the secattr on a socket @@ -2211,7 +2440,7 @@ static int smack_netlabel_send(struct sock *sk, struct sockaddr_in *sap) struct smk_audit_info ad; rcu_read_lock(); - hkp = smack_host_label(sap); + hkp = smack_ipv4host_label(sap); if (hkp != NULL) { #ifdef CONFIG_AUDIT struct lsm_network_audit net; @@ -2236,7 +2465,42 @@ static int smack_netlabel_send(struct sock *sk, struct sockaddr_in *sap) return smack_netlabel(sk, sk_lbl); } -#if IS_ENABLED(CONFIG_IPV6) && !defined(CONFIG_SECURITY_SMACK_NETFILTER) +#if IS_ENABLED(CONFIG_IPV6) +/** + * smk_ipv6_check - check Smack access + * @subject: subject Smack label + * @object: object Smack label + * @address: address + * @act: the action being taken + * + * Check an IPv6 access + */ +static int smk_ipv6_check(struct smack_known *subject, + struct smack_known *object, + struct sockaddr_in6 *address, int act) +{ +#ifdef CONFIG_AUDIT + struct lsm_network_audit net; +#endif + struct smk_audit_info ad; + int rc; + +#ifdef CONFIG_AUDIT + smk_ad_init_net(&ad, __func__, LSM_AUDIT_DATA_NET, &net); + ad.a.u.net->family = PF_INET6; + ad.a.u.net->dport = ntohs(address->sin6_port); + if (act == SMK_RECEIVING) + ad.a.u.net->v6info.saddr = address->sin6_addr; + else + ad.a.u.net->v6info.daddr = address->sin6_addr; +#endif + rc = smk_access(subject, object, MAY_WRITE, &ad); + rc = smk_bu_note("IPv6 check", subject, object, MAY_WRITE, rc); + return rc; +} +#endif /* CONFIG_IPV6 */ + +#ifdef SMACK_IPV6_PORT_LABELING /** * smk_ipv6_port_label - Smack port access table management * @sock: socket @@ -2320,48 +2584,43 @@ static void smk_ipv6_port_label(struct socket *sock, struct sockaddr *address) static int smk_ipv6_port_check(struct sock *sk, struct sockaddr_in6 *address, int act) { - __be16 *bep; - __be32 *be32p; struct smk_port_label *spp; struct socket_smack *ssp = sk->sk_security; - struct smack_known *skp; - unsigned short port = 0; + struct smack_known *skp = NULL; + unsigned short port; struct smack_known *object; - struct smk_audit_info ad; - int rc; -#ifdef CONFIG_AUDIT - struct lsm_network_audit net; -#endif if (act == SMK_RECEIVING) { - skp = smack_net_ambient; + skp = smack_ipv6host_label(address); object = ssp->smk_in; } else { skp = ssp->smk_out; - object = smack_net_ambient; + object = smack_ipv6host_label(address); } /* - * Get the IP address and port from the address. + * The other end is a single label host. */ - port = ntohs(address->sin6_port); - bep = (__be16 *)(&address->sin6_addr); - be32p = (__be32 *)(&address->sin6_addr); + if (skp != NULL && object != NULL) + return smk_ipv6_check(skp, object, address, act); + if (skp == NULL) + skp = smack_net_ambient; + if (object == NULL) + object = smack_net_ambient; /* * It's remote, so port lookup does no good. */ - if (be32p[0] || be32p[1] || be32p[2] || bep[6] || ntohs(bep[7]) != 1) - goto auditout; + if (!smk_ipv6_localhost(address)) + return smk_ipv6_check(skp, object, address, act); /* * It's local so the send check has to have passed. */ - if (act == SMK_RECEIVING) { - skp = &smack_known_web; - goto auditout; - } + if (act == SMK_RECEIVING) + return 0; + port = ntohs(address->sin6_port); list_for_each_entry(spp, &smk_ipv6_port_list, list) { if (spp->smk_port != port) continue; @@ -2371,22 +2630,9 @@ static int smk_ipv6_port_check(struct sock *sk, struct sockaddr_in6 *address, break; } -auditout: - -#ifdef CONFIG_AUDIT - smk_ad_init_net(&ad, __func__, LSM_AUDIT_DATA_NET, &net); - ad.a.u.net->family = sk->sk_family; - ad.a.u.net->dport = port; - if (act == SMK_RECEIVING) - ad.a.u.net->v6info.saddr = address->sin6_addr; - else - ad.a.u.net->v6info.daddr = address->sin6_addr; -#endif - rc = smk_access(skp, object, MAY_WRITE, &ad); - rc = smk_bu_note("IPv6 port check", skp, object, MAY_WRITE, rc); - return rc; + return smk_ipv6_check(skp, object, address, act); } -#endif /* CONFIG_IPV6 && !CONFIG_SECURITY_SMACK_NETFILTER */ +#endif /* SMACK_IPV6_PORT_LABELING */ /** * smack_inode_setsecurity - set smack xattrs @@ -2447,10 +2693,10 @@ static int smack_inode_setsecurity(struct inode *inode, const char *name, } else return -EOPNOTSUPP; -#if IS_ENABLED(CONFIG_IPV6) && !defined(CONFIG_SECURITY_SMACK_NETFILTER) +#ifdef SMACK_IPV6_PORT_LABELING if (sock->sk->sk_family == PF_INET6) smk_ipv6_port_label(sock, NULL); -#endif /* CONFIG_IPV6 && !CONFIG_SECURITY_SMACK_NETFILTER */ +#endif return 0; } @@ -2492,7 +2738,7 @@ static int smack_socket_post_create(struct socket *sock, int family, return smack_netlabel(sock->sk, SMACK_CIPSO_SOCKET); } -#ifndef CONFIG_SECURITY_SMACK_NETFILTER +#ifdef SMACK_IPV6_PORT_LABELING /** * smack_socket_bind - record port binding information. * @sock: the socket @@ -2506,14 +2752,11 @@ static int smack_socket_post_create(struct socket *sock, int family, static int smack_socket_bind(struct socket *sock, struct sockaddr *address, int addrlen) { -#if IS_ENABLED(CONFIG_IPV6) if (sock->sk != NULL && sock->sk->sk_family == PF_INET6) smk_ipv6_port_label(sock, address); -#endif - return 0; } -#endif /* !CONFIG_SECURITY_SMACK_NETFILTER */ +#endif /* SMACK_IPV6_PORT_LABELING */ /** * smack_socket_connect - connect access check @@ -2529,6 +2772,13 @@ static int smack_socket_connect(struct socket *sock, struct sockaddr *sap, int addrlen) { int rc = 0; +#if IS_ENABLED(CONFIG_IPV6) + struct sockaddr_in6 *sip = (struct sockaddr_in6 *)sap; +#endif +#ifdef SMACK_IPV6_SECMARK_LABELING + struct smack_known *rsp; + struct socket_smack *ssp = sock->sk->sk_security; +#endif if (sock->sk == NULL) return 0; @@ -2542,10 +2792,15 @@ static int smack_socket_connect(struct socket *sock, struct sockaddr *sap, case PF_INET6: if (addrlen < sizeof(struct sockaddr_in6)) return -EINVAL; -#if IS_ENABLED(CONFIG_IPV6) && !defined(CONFIG_SECURITY_SMACK_NETFILTER) - rc = smk_ipv6_port_check(sock->sk, (struct sockaddr_in6 *)sap, +#ifdef SMACK_IPV6_SECMARK_LABELING + rsp = smack_ipv6host_label(sip); + if (rsp != NULL) + rc = smk_ipv6_check(ssp->smk_out, rsp, sip, SMK_CONNECTING); -#endif /* CONFIG_IPV6 && !CONFIG_SECURITY_SMACK_NETFILTER */ +#endif +#ifdef SMACK_IPV6_PORT_LABELING + rc = smk_ipv6_port_check(sock->sk, sip, SMK_CONNECTING); +#endif break; } return rc; @@ -3431,9 +3686,13 @@ static int smack_socket_sendmsg(struct socket *sock, struct msghdr *msg, int size) { struct sockaddr_in *sip = (struct sockaddr_in *) msg->msg_name; -#if IS_ENABLED(CONFIG_IPV6) && !defined(CONFIG_SECURITY_SMACK_NETFILTER) +#if IS_ENABLED(CONFIG_IPV6) struct sockaddr_in6 *sap = (struct sockaddr_in6 *) msg->msg_name; -#endif /* CONFIG_IPV6 && !CONFIG_SECURITY_SMACK_NETFILTER */ +#endif +#ifdef SMACK_IPV6_SECMARK_LABELING + struct socket_smack *ssp = sock->sk->sk_security; + struct smack_known *rsp; +#endif int rc = 0; /* @@ -3447,9 +3706,15 @@ static int smack_socket_sendmsg(struct socket *sock, struct msghdr *msg, rc = smack_netlabel_send(sock->sk, sip); break; case AF_INET6: -#if IS_ENABLED(CONFIG_IPV6) && !defined(CONFIG_SECURITY_SMACK_NETFILTER) +#ifdef SMACK_IPV6_SECMARK_LABELING + rsp = smack_ipv6host_label(sap); + if (rsp != NULL) + rc = smk_ipv6_check(ssp->smk_out, rsp, sap, + SMK_CONNECTING); +#endif +#ifdef SMACK_IPV6_PORT_LABELING rc = smk_ipv6_port_check(sock->sk, sap, SMK_SENDING); -#endif /* CONFIG_IPV6 && !CONFIG_SECURITY_SMACK_NETFILTER */ +#endif break; } return rc; @@ -3663,10 +3928,12 @@ access_check: proto = smk_skb_to_addr_ipv6(skb, &sadd); if (proto != IPPROTO_UDP && proto != IPPROTO_TCP) break; -#ifdef CONFIG_SECURITY_SMACK_NETFILTER +#ifdef SMACK_IPV6_SECMARK_LABELING if (skb && skb->secmark != 0) skp = smack_from_secid(skb->secmark); else + skp = smack_ipv6host_label(&sadd); + if (skp == NULL) skp = smack_net_ambient; #ifdef CONFIG_AUDIT smk_ad_init_net(&ad, __func__, LSM_AUDIT_DATA_NET, &net); @@ -3677,9 +3944,10 @@ access_check: rc = smk_access(skp, ssp->smk_in, MAY_WRITE, &ad); rc = smk_bu_note("IPv6 delivery", skp, ssp->smk_in, MAY_WRITE, rc); -#else /* CONFIG_SECURITY_SMACK_NETFILTER */ +#endif /* SMACK_IPV6_SECMARK_LABELING */ +#ifdef SMACK_IPV6_PORT_LABELING rc = smk_ipv6_port_check(sk, &sadd, SMK_RECEIVING); -#endif /* CONFIG_SECURITY_SMACK_NETFILTER */ +#endif /* SMACK_IPV6_PORT_LABELING */ break; #endif /* CONFIG_IPV6 */ } @@ -3777,13 +4045,11 @@ static int smack_socket_getpeersec_dgram(struct socket *sock, } netlbl_secattr_destroy(&secattr); break; -#if IS_ENABLED(CONFIG_IPV6) case PF_INET6: -#ifdef CONFIG_SECURITY_SMACK_NETFILTER +#ifdef SMACK_IPV6_SECMARK_LABELING s = skb->secmark; -#endif /* CONFIG_SECURITY_SMACK_NETFILTER */ +#endif break; -#endif /* CONFIG_IPV6 */ } *secid = s; if (s == 0) @@ -3906,7 +4172,7 @@ access_check: hdr = ip_hdr(skb); addr.sin_addr.s_addr = hdr->saddr; rcu_read_lock(); - hskp = smack_host_label(&addr); + hskp = smack_ipv4host_label(&addr); rcu_read_unlock(); if (hskp == NULL) @@ -4254,7 +4520,7 @@ static int smack_inode_getsecctx(struct inode *inode, void **ctx, u32 *ctxlen) return 0; } -struct security_hook_list smack_hooks[] = { +static struct security_hook_list smack_hooks[] = { LSM_HOOK_INIT(ptrace_access_check, smack_ptrace_access_check), LSM_HOOK_INIT(ptrace_traceme, smack_ptrace_traceme), LSM_HOOK_INIT(syslog, smack_syslog), @@ -4264,6 +4530,8 @@ struct security_hook_list smack_hooks[] = { LSM_HOOK_INIT(sb_copy_data, smack_sb_copy_data), LSM_HOOK_INIT(sb_kern_mount, smack_sb_kern_mount), LSM_HOOK_INIT(sb_statfs, smack_sb_statfs), + LSM_HOOK_INIT(sb_set_mnt_opts, smack_set_mnt_opts), + LSM_HOOK_INIT(sb_parse_opts_str, smack_parse_opts_str), LSM_HOOK_INIT(bprm_set_creds, smack_bprm_set_creds), LSM_HOOK_INIT(bprm_committing_creds, smack_bprm_committing_creds), @@ -4356,9 +4624,9 @@ struct security_hook_list smack_hooks[] = { LSM_HOOK_INIT(unix_may_send, smack_unix_may_send), LSM_HOOK_INIT(socket_post_create, smack_socket_post_create), -#ifndef CONFIG_SECURITY_SMACK_NETFILTER +#ifdef SMACK_IPV6_PORT_LABELING LSM_HOOK_INIT(socket_bind, smack_socket_bind), -#endif /* CONFIG_SECURITY_SMACK_NETFILTER */ +#endif LSM_HOOK_INIT(socket_connect, smack_socket_connect), LSM_HOOK_INIT(socket_sendmsg, smack_socket_sendmsg), LSM_HOOK_INIT(socket_sock_rcv_skb, smack_socket_sock_rcv_skb), @@ -4453,7 +4721,16 @@ static __init int smack_init(void) return -ENOMEM; } - printk(KERN_INFO "Smack: Initializing.\n"); + pr_info("Smack: Initializing.\n"); +#ifdef CONFIG_SECURITY_SMACK_NETFILTER + pr_info("Smack: Netfilter enabled.\n"); +#endif +#ifdef SMACK_IPV6_PORT_LABELING + pr_info("Smack: IPv6 port labeling enabled.\n"); +#endif +#ifdef SMACK_IPV6_SECMARK_LABELING + pr_info("Smack: IPv6 Netfilter enabled.\n"); +#endif /* * Set the security state for the initial task. diff --git a/security/smack/smackfs.c b/security/smack/smackfs.c index 2716d02119f3..c20b154a33f2 100644 --- a/security/smack/smackfs.c +++ b/security/smack/smackfs.c @@ -29,6 +29,7 @@ #include <linux/magic.h> #include "smack.h" +#define BEBITS (sizeof(__be32) * 8) /* * smackfs pseudo filesystem. */ @@ -40,7 +41,7 @@ enum smk_inos { 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_NET4ADDR = 8, /* single label hosts */ SMK_ONLYCAP = 9, /* the only "capable" label */ SMK_LOGGING = 10, /* logging */ SMK_LOAD_SELF = 11, /* task specific rules */ @@ -57,6 +58,9 @@ enum smk_inos { #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 */ }; /* @@ -64,7 +68,10 @@ enum smk_inos { */ static DEFINE_MUTEX(smack_cipso_lock); static DEFINE_MUTEX(smack_ambient_lock); -static DEFINE_MUTEX(smk_netlbladdr_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. @@ -118,7 +125,10 @@ int smack_ptrace_rule = SMACK_PTRACE_DEFAULT; * can write to the specified label. */ -LIST_HEAD(smk_netlbladdr_list); +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. @@ -129,7 +139,7 @@ struct smack_master_list { struct smack_rule *smk_rule; }; -LIST_HEAD(smack_rule_list); +static LIST_HEAD(smack_rule_list); struct smack_parsed_rule { struct smack_known *smk_subject; @@ -140,11 +150,6 @@ struct smack_parsed_rule { static int smk_cipso_doi_value = SMACK_CIPSO_DOI_DEFAULT; -struct smack_known smack_cipso_option = { - .smk_known = SMACK_CIPSO_OPTION, - .smk_secid = 0, -}; - /* * Values for parsing cipso rules * SMK_DIGITLEN: Length of a digit field in a rule. @@ -1047,92 +1052,90 @@ static const struct file_operations smk_cipso2_ops = { * Seq_file read operations for /smack/netlabel */ -static void *netlbladdr_seq_start(struct seq_file *s, loff_t *pos) +static void *net4addr_seq_start(struct seq_file *s, loff_t *pos) { - return smk_seq_start(s, pos, &smk_netlbladdr_list); + return smk_seq_start(s, pos, &smk_net4addr_list); } -static void *netlbladdr_seq_next(struct seq_file *s, void *v, loff_t *pos) +static void *net4addr_seq_next(struct seq_file *s, void *v, loff_t *pos) { - return smk_seq_next(s, v, pos, &smk_netlbladdr_list); + return smk_seq_next(s, v, pos, &smk_net4addr_list); } -#define BEBITS (sizeof(__be32) * 8) /* * Print host/label pairs */ -static int netlbladdr_seq_show(struct seq_file *s, void *v) +static int net4addr_seq_show(struct seq_file *s, void *v) { struct list_head *list = v; - struct smk_netlbladdr *skp = - 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); - - for (maskn = 0; temp_mask; temp_mask <<= 1, maskn++); + struct smk_net4addr *skp = + list_entry_rcu(list, struct smk_net4addr, list); + char *kp = SMACK_CIPSO_OPTION; - seq_printf(s, "%u.%u.%u.%u/%d %s\n", - hp[0], hp[1], hp[2], hp[3], maskn, skp->smk_label->smk_known); + 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 netlbladdr_seq_ops = { - .start = netlbladdr_seq_start, - .next = netlbladdr_seq_next, - .show = netlbladdr_seq_show, +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_netlbladdr - open() for /smack/netlabel + * smk_open_net4addr - open() for /smack/netlabel * @inode: inode structure representing file * @file: "netlabel" file pointer * - * Connect our netlbladdr_seq_* operations with /smack/netlabel + * Connect our net4addr_seq_* operations with /smack/netlabel * file_operations */ -static int smk_open_netlbladdr(struct inode *inode, struct file *file) +static int smk_open_net4addr(struct inode *inode, struct file *file) { - return seq_open(file, &netlbladdr_seq_ops); + return seq_open(file, &net4addr_seq_ops); } /** - * smk_netlbladdr_insert + * smk_net4addr_insert * @new : netlabel to insert * - * This helper insert netlabel in the smack_netlbladdrs list + * This helper insert netlabel in the smack_net4addrs list * sorted by netmask length (longest to smallest) - * locked by &smk_netlbladdr_lock in smk_write_netlbladdr + * locked by &smk_net4addr_lock in smk_write_net4addr * */ -static void smk_netlbladdr_insert(struct smk_netlbladdr *new) +static void smk_net4addr_insert(struct smk_net4addr *new) { - struct smk_netlbladdr *m, *m_next; + struct smk_net4addr *m; + struct smk_net4addr *m_next; - if (list_empty(&smk_netlbladdr_list)) { - list_add_rcu(&new->list, &smk_netlbladdr_list); + if (list_empty(&smk_net4addr_list)) { + list_add_rcu(&new->list, &smk_net4addr_list); return; } - m = list_entry_rcu(smk_netlbladdr_list.next, - struct smk_netlbladdr, list); + m = list_entry_rcu(smk_net4addr_list.next, + struct smk_net4addr, 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); + if (new->smk_masks > m->smk_masks) { + list_add_rcu(&new->list, &smk_net4addr_list); return; } - list_for_each_entry_rcu(m, &smk_netlbladdr_list, list) { - if (list_is_last(&m->list, &smk_netlbladdr_list)) { + 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_netlbladdr, list); - if (new->smk_mask.s_addr > m_next->smk_mask.s_addr) { + struct smk_net4addr, list); + if (new->smk_masks > m_next->smk_masks) { list_add_rcu(&new->list, &m->list); return; } @@ -1141,28 +1144,29 @@ static void smk_netlbladdr_insert(struct smk_netlbladdr *new) /** - * smk_write_netlbladdr - write() for /smack/netlabel + * 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 netlbladdr per write call. + * Accepts only one net4addr 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, +static ssize_t smk_write_net4addr(struct file *file, const char __user *buf, size_t count, loff_t *ppos) { - struct smk_netlbladdr *snp; + struct smk_net4addr *snp; struct sockaddr_in newname; char *smack; - struct smack_known *skp; + 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; @@ -1200,7 +1204,7 @@ static ssize_t smk_write_netlbladdr(struct file *file, const char __user *buf, data[count] = '\0'; rc = sscanf(data, "%hhd.%hhd.%hhd.%hhd/%u %s", - &host[0], &host[1], &host[2], &host[3], &m, smack); + &host[0], &host[1], &host[2], &host[3], &masks, smack); if (rc != 6) { rc = sscanf(data, "%hhd.%hhd.%hhd.%hhd %s", &host[0], &host[1], &host[2], &host[3], smack); @@ -1209,8 +1213,9 @@ static ssize_t smk_write_netlbladdr(struct file *file, const char __user *buf, goto free_out; } m = BEBITS; + masks = 32; } - if (m > BEBITS) { + if (masks > BEBITS) { rc = -EINVAL; goto free_out; } @@ -1225,16 +1230,16 @@ static ssize_t smk_write_netlbladdr(struct file *file, const char __user *buf, goto free_out; } } else { - /* check known options */ - if (strcmp(smack, smack_cipso_option.smk_known) == 0) - skp = &smack_cipso_option; - else { + /* + * Only the -CIPSO option is supported for IPv4 + */ + if (strcmp(smack, SMACK_CIPSO_OPTION) != 0) { rc = -EINVAL; goto free_out; } } - for (temp_mask = 0; m > 0; m--) { + for (m = masks, temp_mask = 0; m > 0; m--) { temp_mask |= mask_bits; mask_bits >>= 1; } @@ -1245,14 +1250,13 @@ static ssize_t smk_write_netlbladdr(struct file *file, const char __user *buf, * Only allow one writer at a time. Writes should be * quite rare and small in any case. */ - mutex_lock(&smk_netlbladdr_lock); + mutex_lock(&smk_net4addr_lock); nsa = newname.sin_addr.s_addr; /* try to find if the prefix is already in the list */ found = 0; - list_for_each_entry_rcu(snp, &smk_netlbladdr_list, list) { - if (snp->smk_host.sin_addr.s_addr == nsa && - snp->smk_mask.s_addr == mask.s_addr) { + list_for_each_entry_rcu(snp, &smk_net4addr_list, list) { + if (snp->smk_host.s_addr == nsa && snp->smk_masks == masks) { found = 1; break; } @@ -1265,17 +1269,20 @@ static ssize_t smk_write_netlbladdr(struct file *file, const char __user *buf, rc = -ENOMEM; else { rc = 0; - snp->smk_host.sin_addr.s_addr = newname.sin_addr.s_addr; + snp->smk_host.s_addr = newname.sin_addr.s_addr; snp->smk_mask.s_addr = mask.s_addr; snp->smk_label = skp; - smk_netlbladdr_insert(snp); + snp->smk_masks = masks; + smk_net4addr_insert(snp); } } else { - /* we delete the unlabeled entry, only if the previous label - * wasn't the special CIPSO option */ - if (snp->smk_label != &smack_cipso_option) + /* + * Delete the unlabeled entry, only if the previous label + * wasn't the special CIPSO option + */ + if (snp->smk_label != NULL) rc = netlbl_cfg_unlbl_static_del(&init_net, NULL, - &snp->smk_host.sin_addr, &snp->smk_mask, + &snp->smk_host, &snp->smk_mask, PF_INET, &audit_info); else rc = 0; @@ -1287,15 +1294,15 @@ static ssize_t smk_write_netlbladdr(struct file *file, const char __user *buf, * this host so that incoming packets get labeled. * but only if we didn't get the special CIPSO option */ - if (rc == 0 && skp != &smack_cipso_option) + if (rc == 0 && skp != NULL) rc = netlbl_cfg_unlbl_static_add(&init_net, NULL, - &snp->smk_host.sin_addr, &snp->smk_mask, PF_INET, + &snp->smk_host, &snp->smk_mask, PF_INET, snp->smk_label->smk_secid, &audit_info); if (rc == 0) rc = count; - mutex_unlock(&smk_netlbladdr_lock); + mutex_unlock(&smk_net4addr_lock); free_out: kfree(smack); @@ -1305,14 +1312,279 @@ free_data_out: return rc; } -static const struct file_operations smk_netlbladdr_ops = { - .open = smk_open_netlbladdr, +static const struct file_operations smk_net4addr_ops = { + .open = smk_open_net4addr, .read = seq_read, .llseek = seq_lseek, - .write = smk_write_netlbladdr, + .write = smk_write_net4addr, .release = seq_release, }; +#if IS_ENABLED(CONFIG_IPV6) +/* + * Seq_file read operations for /smack/netlabel6 + */ + +static void *net6addr_seq_start(struct seq_file *s, loff_t *pos) +{ + return smk_seq_start(s, pos, &smk_net6addr_list); +} + +static void *net6addr_seq_next(struct seq_file *s, void *v, loff_t *pos) +{ + return smk_seq_next(s, v, pos, &smk_net6addr_list); +} + +/* + * Print host/label pairs + */ +static int net6addr_seq_show(struct seq_file *s, void *v) +{ + struct list_head *list = v; + struct smk_net6addr *skp = + list_entry(list, struct smk_net6addr, list); + + if (skp->smk_label != NULL) + seq_printf(s, "%pI6/%d %s\n", &skp->smk_host, skp->smk_masks, + skp->smk_label->smk_known); + + return 0; +} + +static const struct seq_operations net6addr_seq_ops = { + .start = net6addr_seq_start, + .next = net6addr_seq_next, + .show = net6addr_seq_show, + .stop = smk_seq_stop, +}; + +/** + * smk_open_net6addr - open() for /smack/netlabel + * @inode: inode structure representing file + * @file: "netlabel" file pointer + * + * Connect our net6addr_seq_* operations with /smack/netlabel + * file_operations + */ +static int smk_open_net6addr(struct inode *inode, struct file *file) +{ + return seq_open(file, &net6addr_seq_ops); +} + +/** + * smk_net6addr_insert + * @new : entry to insert + * + * This inserts an entry in the smack_net6addrs list + * sorted by netmask length (longest to smallest) + * locked by &smk_net6addr_lock in smk_write_net6addr + * + */ +static void smk_net6addr_insert(struct smk_net6addr *new) +{ + struct smk_net6addr *m_next; + struct smk_net6addr *m; + + if (list_empty(&smk_net6addr_list)) { + list_add_rcu(&new->list, &smk_net6addr_list); + return; + } + + m = list_entry_rcu(smk_net6addr_list.next, + struct smk_net6addr, list); + + if (new->smk_masks > m->smk_masks) { + list_add_rcu(&new->list, &smk_net6addr_list); + return; + } + + list_for_each_entry_rcu(m, &smk_net6addr_list, list) { + if (list_is_last(&m->list, &smk_net6addr_list)) { + list_add_rcu(&new->list, &m->list); + return; + } + m_next = list_entry_rcu(m->list.next, + struct smk_net6addr, list); + if (new->smk_masks > m_next->smk_masks) { + list_add_rcu(&new->list, &m->list); + return; + } + } +} + + +/** + * smk_write_net6addr - 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 net6addr per write call. + * Returns number of bytes written or error code, as appropriate + */ +static ssize_t smk_write_net6addr(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + struct smk_net6addr *snp; + struct in6_addr newname; + struct in6_addr fullmask; + struct smack_known *skp = NULL; + char *smack; + char *data; + int rc = 0; + int found = 0; + int i; + unsigned int scanned[8]; + unsigned int m; + unsigned int mask = 128; + + /* + * Must have privilege. + * No partial writes. + * Enough data must be present. + * "<addr/mask, as a:b:c:d:e:f:g:h/e><space><label>" + * "<addr, as a:b:c:d:e:f:g:h><space><label>" + */ + if (!smack_privileged(CAP_MAC_ADMIN)) + return -EPERM; + if (*ppos != 0) + return -EINVAL; + if (count < SMK_NETLBLADDRMIN) + return -EINVAL; + + data = kzalloc(count + 1, GFP_KERNEL); + if (data == NULL) + return -ENOMEM; + + if (copy_from_user(data, buf, count) != 0) { + rc = -EFAULT; + goto free_data_out; + } + + smack = kzalloc(count + 1, GFP_KERNEL); + if (smack == NULL) { + rc = -ENOMEM; + goto free_data_out; + } + + data[count] = '\0'; + + i = sscanf(data, "%x:%x:%x:%x:%x:%x:%x:%x/%u %s", + &scanned[0], &scanned[1], &scanned[2], &scanned[3], + &scanned[4], &scanned[5], &scanned[6], &scanned[7], + &mask, smack); + if (i != 10) { + i = sscanf(data, "%x:%x:%x:%x:%x:%x:%x:%x %s", + &scanned[0], &scanned[1], &scanned[2], + &scanned[3], &scanned[4], &scanned[5], + &scanned[6], &scanned[7], smack); + if (i != 9) { + rc = -EINVAL; + goto free_out; + } + } + if (mask > 128) { + rc = -EINVAL; + goto free_out; + } + for (i = 0; i < 8; i++) { + if (scanned[i] > 0xffff) { + rc = -EINVAL; + goto free_out; + } + newname.s6_addr16[i] = htons(scanned[i]); + } + + /* + * If smack begins with '-', it is an option, don't import it + */ + if (smack[0] != '-') { + skp = smk_import_entry(smack, 0); + if (skp == NULL) { + rc = -EINVAL; + goto free_out; + } + } else { + /* + * Only -DELETE is supported for IPv6 + */ + if (strcmp(smack, SMACK_DELETE_OPTION) != 0) { + rc = -EINVAL; + goto free_out; + } + } + + for (i = 0, m = mask; i < 8; i++) { + if (m >= 16) { + fullmask.s6_addr16[i] = 0xffff; + m -= 16; + } else if (m > 0) { + fullmask.s6_addr16[i] = (1 << m) - 1; + m = 0; + } else + fullmask.s6_addr16[i] = 0; + newname.s6_addr16[i] &= fullmask.s6_addr16[i]; + } + + /* + * Only allow one writer at a time. Writes should be + * quite rare and small in any case. + */ + mutex_lock(&smk_net6addr_lock); + /* + * Try to find the prefix in the list + */ + list_for_each_entry_rcu(snp, &smk_net6addr_list, list) { + if (mask != snp->smk_masks) + continue; + for (found = 1, i = 0; i < 8; i++) { + if (newname.s6_addr16[i] != + snp->smk_host.s6_addr16[i]) { + found = 0; + break; + } + } + if (found == 1) + break; + } + if (found == 0) { + snp = kzalloc(sizeof(*snp), GFP_KERNEL); + if (snp == NULL) + rc = -ENOMEM; + else { + snp->smk_host = newname; + snp->smk_mask = fullmask; + snp->smk_masks = mask; + snp->smk_label = skp; + smk_net6addr_insert(snp); + } + } else { + snp->smk_label = skp; + } + + if (rc == 0) + rc = count; + + mutex_unlock(&smk_net6addr_lock); + +free_out: + kfree(smack); +free_data_out: + kfree(data); + + return rc; +} + +static const struct file_operations smk_net6addr_ops = { + .open = smk_open_net6addr, + .read = seq_read, + .llseek = seq_lseek, + .write = smk_write_net6addr, + .release = seq_release, +}; +#endif /* CONFIG_IPV6 */ + /** * smk_read_doi - read() for /smack/doi * @filp: file pointer, not actually used @@ -2320,11 +2592,7 @@ static const struct file_operations smk_revoke_subj_ops = { */ static int smk_init_sysfs(void) { - int err; - err = sysfs_create_mount_point(fs_kobj, "smackfs"); - if (err) - return err; - return 0; + return sysfs_create_mount_point(fs_kobj, "smackfs"); } /** @@ -2519,8 +2787,8 @@ static int smk_fill_super(struct super_block *sb, void *data, int silent) "direct", &smk_direct_ops, S_IRUGO|S_IWUSR}, [SMK_AMBIENT] = { "ambient", &smk_ambient_ops, S_IRUGO|S_IWUSR}, - [SMK_NETLBLADDR] = { - "netlabel", &smk_netlbladdr_ops, S_IRUGO|S_IWUSR}, + [SMK_NET4ADDR] = { + "netlabel", &smk_net4addr_ops, S_IRUGO|S_IWUSR}, [SMK_ONLYCAP] = { "onlycap", &smk_onlycap_ops, S_IRUGO|S_IWUSR}, [SMK_LOGGING] = { @@ -2552,6 +2820,10 @@ static int smk_fill_super(struct super_block *sb, void *data, int silent) [SMK_UNCONFINED] = { "unconfined", &smk_unconfined_ops, S_IRUGO|S_IWUSR}, #endif +#if IS_ENABLED(CONFIG_IPV6) + [SMK_NET6ADDR] = { + "ipv6host", &smk_net6addr_ops, S_IRUGO|S_IWUSR}, +#endif /* CONFIG_IPV6 */ /* last one */ {""} }; diff --git a/security/yama/Kconfig b/security/yama/Kconfig index 3123e1da2fed..90c605eea892 100644 --- a/security/yama/Kconfig +++ b/security/yama/Kconfig @@ -6,14 +6,7 @@ config SECURITY_YAMA This selects Yama, which extends DAC support with additional system-wide security settings beyond regular Linux discretionary access controls. Currently available is ptrace scope restriction. + Like capabilities, this security module stacks with other LSMs. Further information can be found in Documentation/security/Yama.txt. If you are unsure how to answer this question, answer N. - -config SECURITY_YAMA_STACKED - bool "Yama stacked with other LSMs" - depends on SECURITY_YAMA - default n - help - When Yama is built into the kernel, force it to stack with the - selected primary LSM. diff --git a/security/yama/yama_lsm.c b/security/yama/yama_lsm.c index 5ebb89687936..d3c19c970a06 100644 --- a/security/yama/yama_lsm.c +++ b/security/yama/yama_lsm.c @@ -353,11 +353,6 @@ static struct security_hook_list yama_hooks[] = { LSM_HOOK_INIT(task_free, yama_task_free), }; -void __init yama_add_hooks(void) -{ - security_add_hooks(yama_hooks, ARRAY_SIZE(yama_hooks)); -} - #ifdef CONFIG_SYSCTL static int yama_dointvec_minmax(struct ctl_table *table, int write, void __user *buffer, size_t *lenp, loff_t *ppos) @@ -396,26 +391,18 @@ static struct ctl_table yama_sysctl_table[] = { }, { } }; -#endif /* CONFIG_SYSCTL */ - -static __init int yama_init(void) +static void __init yama_init_sysctl(void) { -#ifndef CONFIG_SECURITY_YAMA_STACKED - /* - * If yama is being stacked this is already taken care of. - */ - if (!security_module_enable("yama")) - return 0; - yama_add_hooks(); -#endif - pr_info("Yama: becoming mindful.\n"); - -#ifdef CONFIG_SYSCTL if (!register_sysctl_paths(yama_sysctl_path, yama_sysctl_table)) panic("Yama: sysctl registration failed.\n"); -#endif - - return 0; } +#else +static inline void yama_init_sysctl(void) { } +#endif /* CONFIG_SYSCTL */ -security_initcall(yama_init); +void __init yama_add_hooks(void) +{ + pr_info("Yama: becoming mindful.\n"); + security_add_hooks(yama_hooks, ARRAY_SIZE(yama_hooks)); + yama_init_sysctl(); +} |