diff options
author | James Morris <jmorris@namei.org> | 2009-01-07 09:58:22 +1100 |
---|---|---|
committer | James Morris <jmorris@namei.org> | 2009-01-07 09:58:22 +1100 |
commit | ac8cc0fa5395fe2278e305a4cbed48e90d88d878 (patch) | |
tree | 515f577bfddd054ee4373228be7c974dfb8133af /security | |
parent | 238c6d54830c624f34ac9cf123ac04aebfca5013 (diff) | |
parent | 3699c53c485bf0168e6500d0ed18bf931584dd7c (diff) |
Merge branch 'next' into for-linus
Diffstat (limited to 'security')
-rw-r--r-- | security/commoncap.c | 29 | ||||
-rw-r--r-- | security/keys/keyctl.c | 2 | ||||
-rw-r--r-- | security/security.c | 26 | ||||
-rw-r--r-- | security/selinux/Kconfig | 27 | ||||
-rw-r--r-- | security/selinux/avc.c | 16 | ||||
-rw-r--r-- | security/selinux/hooks.c | 22 | ||||
-rw-r--r-- | security/selinux/include/avc_ss.h | 4 | ||||
-rw-r--r-- | security/selinux/selinuxfs.c | 16 | ||||
-rw-r--r-- | security/selinux/ss/context.h | 2 | ||||
-rw-r--r-- | security/smack/smack.h | 31 | ||||
-rw-r--r-- | security/smack/smack_access.c | 28 | ||||
-rw-r--r-- | security/smack/smack_lsm.c | 310 | ||||
-rw-r--r-- | security/smack/smackfs.c | 369 |
13 files changed, 616 insertions, 266 deletions
diff --git a/security/commoncap.c b/security/commoncap.c index 69fc9952650f..7cd61a5f5205 100644 --- a/security/commoncap.c +++ b/security/commoncap.c @@ -45,26 +45,22 @@ EXPORT_SYMBOL(cap_netlink_recv); /** * cap_capable - Determine whether a task has a particular effective capability * @tsk: The task to query + * @cred: The credentials to use * @cap: The capability to check for * @audit: Whether to write an audit message or not * * Determine whether the nominated task has the specified capability amongst * its effective set, returning 0 if it does, -ve if it does not. * - * NOTE WELL: cap_capable() cannot be used like the kernel's capable() - * function. That is, it has the reverse semantics: cap_capable() returns 0 - * when a task has a capability, but the kernel's capable() returns 1 for this - * case. + * NOTE WELL: cap_has_capability() cannot be used like the kernel's capable() + * and has_capability() functions. That is, it has the reverse semantics: + * cap_has_capability() returns 0 when a task has a capability, but the + * kernel's capable() and has_capability() returns 1 for this case. */ -int cap_capable(struct task_struct *tsk, int cap, int audit) +int cap_capable(struct task_struct *tsk, const struct cred *cred, int cap, + int audit) { - __u32 cap_raised; - - /* Derived from include/linux/sched.h:capable. */ - rcu_read_lock(); - cap_raised = cap_raised(__task_cred(tsk)->cap_effective, cap); - rcu_read_unlock(); - return cap_raised ? 0 : -EPERM; + return cap_raised(cred->cap_effective, cap) ? 0 : -EPERM; } /** @@ -160,7 +156,8 @@ static inline int cap_inh_is_capped(void) /* they are so limited unless the current task has the CAP_SETPCAP * capability */ - if (cap_capable(current, CAP_SETPCAP, SECURITY_CAP_AUDIT) == 0) + if (cap_capable(current, current_cred(), CAP_SETPCAP, + SECURITY_CAP_AUDIT) == 0) return 0; #endif return 1; @@ -869,7 +866,8 @@ int cap_task_prctl(int option, unsigned long arg2, unsigned long arg3, & (new->securebits ^ arg2)) /*[1]*/ || ((new->securebits & SECURE_ALL_LOCKS & ~arg2)) /*[2]*/ || (arg2 & ~(SECURE_ALL_LOCKS | SECURE_ALL_BITS)) /*[3]*/ - || (cap_capable(current, CAP_SETPCAP, SECURITY_CAP_AUDIT) != 0) /*[4]*/ + || (cap_capable(current, current_cred(), CAP_SETPCAP, + SECURITY_CAP_AUDIT) != 0) /*[4]*/ /* * [1] no changing of bits that are locked * [2] no unlocking of locks @@ -950,7 +948,8 @@ int cap_vm_enough_memory(struct mm_struct *mm, long pages) { int cap_sys_admin = 0; - if (cap_capable(current, CAP_SYS_ADMIN, SECURITY_CAP_NOAUDIT) == 0) + if (cap_capable(current, current_cred(), CAP_SYS_ADMIN, + SECURITY_CAP_NOAUDIT) == 0) cap_sys_admin = 1; return __vm_enough_memory(mm, pages, cap_sys_admin); } diff --git a/security/keys/keyctl.c b/security/keys/keyctl.c index 6688765bd8b9..09796797d122 100644 --- a/security/keys/keyctl.c +++ b/security/keys/keyctl.c @@ -1294,7 +1294,7 @@ asmlinkage long sys_keyctl(int option, unsigned long arg2, unsigned long arg3, case KEYCTL_GET_SECURITY: return keyctl_get_security((key_serial_t) arg2, - (char *) arg3, + (char __user *) arg3, (size_t) arg4); default: diff --git a/security/security.c b/security/security.c index 678d4d07b852..c3586c0d97e2 100644 --- a/security/security.c +++ b/security/security.c @@ -154,14 +154,32 @@ int security_capset(struct cred *new, const struct cred *old, effective, inheritable, permitted); } -int security_capable(struct task_struct *tsk, int cap) +int security_capable(int cap) { - return security_ops->capable(tsk, cap, SECURITY_CAP_AUDIT); + return security_ops->capable(current, current_cred(), cap, + SECURITY_CAP_AUDIT); } -int security_capable_noaudit(struct task_struct *tsk, int cap) +int security_real_capable(struct task_struct *tsk, int cap) { - return security_ops->capable(tsk, cap, SECURITY_CAP_NOAUDIT); + const struct cred *cred; + int ret; + + cred = get_task_cred(tsk); + ret = security_ops->capable(tsk, cred, cap, SECURITY_CAP_AUDIT); + put_cred(cred); + return ret; +} + +int security_real_capable_noaudit(struct task_struct *tsk, int cap) +{ + const struct cred *cred; + int ret; + + cred = get_task_cred(tsk); + ret = security_ops->capable(tsk, cred, cap, SECURITY_CAP_NOAUDIT); + put_cred(cred); + return ret; } int security_acct(struct file *file) diff --git a/security/selinux/Kconfig b/security/selinux/Kconfig index 26301dd651d3..bca1b74a4a2f 100644 --- a/security/selinux/Kconfig +++ b/security/selinux/Kconfig @@ -94,33 +94,6 @@ config SECURITY_SELINUX_CHECKREQPROT_VALUE If you are unsure how to answer this question, answer 1. -config SECURITY_SELINUX_ENABLE_SECMARK_DEFAULT - bool "NSA SELinux enable new secmark network controls by default" - depends on SECURITY_SELINUX - default n - help - This option determines whether the new secmark-based network - controls will be enabled by default. If not, the old internal - per-packet controls will be enabled by default, preserving - old behavior. - - If you enable the new controls, you will need updated - SELinux userspace libraries, tools and policy. Typically, - your distribution will provide these and enable the new controls - in the kernel they also distribute. - - Note that this option can be overridden at boot with the - selinux_compat_net parameter, and after boot via - /selinux/compat_net. See Documentation/kernel-parameters.txt - for details on this parameter. - - If you enable the new network controls, you will likely - also require the SECMARK and CONNSECMARK targets, as - well as any conntrack helpers for protocols which you - wish to control. - - If you are unsure what to do here, select N. - config SECURITY_SELINUX_POLICYDB_VERSION_MAX bool "NSA SELinux maximum supported policy format version" depends on SECURITY_SELINUX diff --git a/security/selinux/avc.c b/security/selinux/avc.c index d43bd6baeeaa..eb41f43e2772 100644 --- a/security/selinux/avc.c +++ b/security/selinux/avc.c @@ -53,18 +53,20 @@ static const char *class_to_string[] = { #undef S_ static const struct av_inherit av_inherit[] = { -#define S_(c, i, b) { c, common_##i##_perm_to_string, b }, +#define S_(c, i, b) { .tclass = c,\ + .common_pts = common_##i##_perm_to_string,\ + .common_base = b }, #include "av_inherit.h" #undef S_ }; const struct selinux_class_perm selinux_class_perm = { - av_perm_to_string, - ARRAY_SIZE(av_perm_to_string), - class_to_string, - ARRAY_SIZE(class_to_string), - av_inherit, - ARRAY_SIZE(av_inherit) + .av_perm_to_string = av_perm_to_string, + .av_pts_len = ARRAY_SIZE(av_perm_to_string), + .class_to_string = class_to_string, + .cts_len = ARRAY_SIZE(class_to_string), + .av_inherit = av_inherit, + .av_inherit_len = ARRAY_SIZE(av_inherit) }; #define AVC_CACHE_SLOTS 512 diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index dbeaa783b2a9..00815973d412 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -1433,12 +1433,13 @@ static int current_has_perm(const struct task_struct *tsk, /* Check whether a task is allowed to use a capability. */ static int task_has_capability(struct task_struct *tsk, + const struct cred *cred, int cap, int audit) { struct avc_audit_data ad; struct av_decision avd; u16 sclass; - u32 sid = task_sid(tsk); + u32 sid = cred_sid(cred); u32 av = CAP_TO_MASK(cap); int rc; @@ -1865,15 +1866,16 @@ static int selinux_capset(struct cred *new, const struct cred *old, return cred_has_perm(old, new, PROCESS__SETCAP); } -static int selinux_capable(struct task_struct *tsk, int cap, int audit) +static int selinux_capable(struct task_struct *tsk, const struct cred *cred, + int cap, int audit) { int rc; - rc = secondary_ops->capable(tsk, cap, audit); + rc = secondary_ops->capable(tsk, cred, cap, audit); if (rc) return rc; - return task_has_capability(tsk, cap, audit); + return task_has_capability(tsk, cred, cap, audit); } static int selinux_sysctl_get_sid(ctl_table *table, u16 tclass, u32 *sid) @@ -2037,7 +2039,8 @@ static int selinux_vm_enough_memory(struct mm_struct *mm, long pages) { int rc, cap_sys_admin = 0; - rc = selinux_capable(current, CAP_SYS_ADMIN, SECURITY_CAP_NOAUDIT); + rc = selinux_capable(current, current_cred(), CAP_SYS_ADMIN, + SECURITY_CAP_NOAUDIT); if (rc == 0) cap_sys_admin = 1; @@ -2880,7 +2883,8 @@ static int selinux_inode_getsecurity(const struct inode *inode, const char *name * and lack of permission just means that we fall back to the * in-core context value, not a denial. */ - error = selinux_capable(current, CAP_MAC_ADMIN, SECURITY_CAP_NOAUDIT); + error = selinux_capable(current, current_cred(), CAP_MAC_ADMIN, + SECURITY_CAP_NOAUDIT); if (!error) error = security_sid_to_context_force(isec->sid, &context, &size); @@ -4185,7 +4189,7 @@ static int selinux_sock_rcv_skb_iptables_compat(struct sock *sk, static int selinux_sock_rcv_skb_compat(struct sock *sk, struct sk_buff *skb, u16 family) { - int err; + int err = 0; struct sk_security_struct *sksec = sk->sk_security; u32 peer_sid; u32 sk_sid = sksec->sid; @@ -4202,7 +4206,7 @@ static int selinux_sock_rcv_skb_compat(struct sock *sk, struct sk_buff *skb, if (selinux_compat_net) err = selinux_sock_rcv_skb_iptables_compat(sk, skb, &ad, family, addrp); - else + else if (selinux_secmark_enabled()) err = avc_has_perm(sk_sid, skb->secmark, SECCLASS_PACKET, PACKET__RECV, &ad); if (err) @@ -4705,7 +4709,7 @@ static unsigned int selinux_ip_postroute_compat(struct sk_buff *skb, if (selinux_ip_postroute_iptables_compat(skb->sk, ifindex, &ad, family, addrp)) return NF_DROP; - } else { + } else if (selinux_secmark_enabled()) { if (avc_has_perm(sksec->sid, skb->secmark, SECCLASS_PACKET, PACKET__SEND, &ad)) return NF_DROP; diff --git a/security/selinux/include/avc_ss.h b/security/selinux/include/avc_ss.h index c0d314d9f8e1..bb1ec801bdfe 100644 --- a/security/selinux/include/avc_ss.h +++ b/security/selinux/include/avc_ss.h @@ -17,16 +17,16 @@ struct av_perm_to_string { }; struct av_inherit { - u16 tclass; const char **common_pts; u32 common_base; + u16 tclass; }; struct selinux_class_perm { const struct av_perm_to_string *av_perm_to_string; u32 av_pts_len; - const char **class_to_string; u32 cts_len; + const char **class_to_string; const struct av_inherit *av_inherit; u32 av_inherit_len; }; diff --git a/security/selinux/selinuxfs.c b/security/selinux/selinuxfs.c index 8f612c8becb5..01ec6d2c6b97 100644 --- a/security/selinux/selinuxfs.c +++ b/security/selinux/selinuxfs.c @@ -47,13 +47,7 @@ static char *policycap_names[] = { unsigned int selinux_checkreqprot = CONFIG_SECURITY_SELINUX_CHECKREQPROT_VALUE; -#ifdef CONFIG_SECURITY_SELINUX_ENABLE_SECMARK_DEFAULT -#define SELINUX_COMPAT_NET_VALUE 0 -#else -#define SELINUX_COMPAT_NET_VALUE 1 -#endif - -int selinux_compat_net = SELINUX_COMPAT_NET_VALUE; +int selinux_compat_net = 0; static int __init checkreqprot_setup(char *str) { @@ -494,7 +488,13 @@ static ssize_t sel_write_compat_net(struct file *file, const char __user *buf, if (sscanf(page, "%d", &new_value) != 1) goto out; - selinux_compat_net = new_value ? 1 : 0; + if (new_value) { + printk(KERN_NOTICE + "SELinux: compat_net is deprecated, please use secmark" + " instead\n"); + selinux_compat_net = 1; + } else + selinux_compat_net = 0; length = count; out: free_page((unsigned long) page); diff --git a/security/selinux/ss/context.h b/security/selinux/ss/context.h index 658c2bd17da8..d9dd7a2f6a8a 100644 --- a/security/selinux/ss/context.h +++ b/security/selinux/ss/context.h @@ -27,9 +27,9 @@ struct context { u32 user; u32 role; u32 type; + u32 len; /* length of string in bytes */ struct mls_range range; char *str; /* string representation if context cannot be mapped. */ - u32 len; /* length of string in bytes */ }; static inline void mls_context_init(struct context *c) diff --git a/security/smack/smack.h b/security/smack/smack.h index 31dce559595a..b79582e4fbfd 100644 --- a/security/smack/smack.h +++ b/security/smack/smack.h @@ -16,6 +16,7 @@ #include <linux/capability.h> #include <linux/spinlock.h> #include <linux/security.h> +#include <linux/in.h> #include <net/netlabel.h> /* @@ -39,6 +40,7 @@ struct superblock_smack { struct socket_smack { char *smk_out; /* outbound label */ char *smk_in; /* inbound label */ + int smk_labeled; /* label scheme */ char smk_packet[SMK_LABELLEN]; /* TCP peer label */ }; @@ -80,6 +82,16 @@ struct smack_cipso { }; /* + * An entry in the table identifying hosts. + */ +struct smk_netlbladdr { + struct smk_netlbladdr *smk_next; + struct sockaddr_in smk_host; /* network address */ + struct in_addr smk_mask; /* network mask */ + char *smk_label; /* label */ +}; + +/* * This is the repository for labels seen so that it is * not necessary to keep allocating tiny chuncks of memory * and so that they can be shared. @@ -127,6 +139,20 @@ struct smack_known { #define XATTR_NAME_SMACKIPOUT XATTR_SECURITY_PREFIX XATTR_SMACK_IPOUT /* + * How communications on this socket are treated. + * Usually it's determined by the underlying netlabel code + * but there are certain cases, including single label hosts + * and potentially single label interfaces for which the + * treatment can not be known in advance. + * + * The possibility of additional labeling schemes being + * introduced in the future exists as well. + */ +#define SMACK_UNLABELED_SOCKET 0 +#define SMACK_CIPSO_SOCKET 1 + +/* + * smackfs magic number * smackfs macic number */ #define SMACK_MAGIC 0x43415d53 /* "SMAC" */ @@ -141,6 +167,7 @@ struct smack_known { * CIPSO defaults. */ #define SMACK_CIPSO_DOI_DEFAULT 3 /* Historical */ +#define SMACK_CIPSO_DOI_INVALID -1 /* Not a DOI */ #define SMACK_CIPSO_DIRECT_DEFAULT 250 /* Arbitrary */ #define SMACK_CIPSO_MAXCATVAL 63 /* Bigger gets harder */ #define SMACK_CIPSO_MAXLEVEL 255 /* CIPSO 2.2 standard */ @@ -176,7 +203,6 @@ u32 smack_to_secid(const char *); * Shared data. */ extern int smack_cipso_direct; -extern int smack_net_nltype; extern char *smack_net_ambient; extern char *smack_onlycap; @@ -186,9 +212,10 @@ extern struct smack_known smack_known_hat; extern struct smack_known smack_known_huh; extern struct smack_known smack_known_invalid; extern struct smack_known smack_known_star; -extern struct smack_known smack_known_unset; +extern struct smack_known smack_known_web; extern struct smk_list_entry *smack_list; +extern struct smk_netlbladdr *smack_netlbladdrs; extern struct security_operations smack_ops; /* diff --git a/security/smack/smack_access.c b/security/smack/smack_access.c index 247cec3b5a43..2e0b83e77ffe 100644 --- a/security/smack/smack_access.c +++ b/security/smack/smack_access.c @@ -15,15 +15,8 @@ #include <linux/sched.h> #include "smack.h" -struct smack_known smack_known_unset = { - .smk_next = NULL, - .smk_known = "UNSET", - .smk_secid = 1, - .smk_cipso = NULL, -}; - struct smack_known smack_known_huh = { - .smk_next = &smack_known_unset, + .smk_next = NULL, .smk_known = "?", .smk_secid = 2, .smk_cipso = NULL, @@ -57,7 +50,14 @@ struct smack_known smack_known_invalid = { .smk_cipso = NULL, }; -struct smack_known *smack_known = &smack_known_invalid; +struct smack_known smack_known_web = { + .smk_next = &smack_known_invalid, + .smk_known = "@", + .smk_secid = 7, + .smk_cipso = NULL, +}; + +struct smack_known *smack_known = &smack_known_web; /* * The initial value needs to be bigger than any of the @@ -99,6 +99,16 @@ int smk_access(char *subject_label, char *object_label, int request) strcmp(subject_label, smack_known_star.smk_known) == 0) return -EACCES; /* + * An internet object can be accessed by any subject. + * Tasks cannot be assigned the internet label. + * An internet subject can access any object. + */ + if (object_label == smack_known_web.smk_known || + subject_label == smack_known_web.smk_known || + strcmp(object_label, smack_known_web.smk_known) == 0 || + strcmp(subject_label, smack_known_web.smk_known) == 0) + return 0; + /* * A star object can be accessed by any subject. */ if (object_label == smack_known_star.smk_known || diff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c index 848212fd4845..0278bc083044 100644 --- a/security/smack/smack_lsm.c +++ b/security/smack/smack_lsm.c @@ -1277,6 +1277,7 @@ static int smack_sk_alloc_security(struct sock *sk, int family, gfp_t gfp_flags) ssp->smk_in = csp; ssp->smk_out = csp; + ssp->smk_labeled = SMACK_CIPSO_SOCKET; ssp->smk_packet[0] = '\0'; sk->sk_security = ssp; @@ -1341,45 +1342,69 @@ static void smack_to_secattr(char *smack, struct netlbl_lsm_secattr *nlsp) struct smack_cipso cipso; int rc; - switch (smack_net_nltype) { - case NETLBL_NLTYPE_CIPSOV4: - nlsp->domain = smack; - nlsp->flags = NETLBL_SECATTR_DOMAIN | NETLBL_SECATTR_MLS_LVL; + nlsp->domain = smack; + nlsp->flags = NETLBL_SECATTR_DOMAIN | NETLBL_SECATTR_MLS_LVL; - rc = smack_to_cipso(smack, &cipso); - if (rc == 0) { - nlsp->attr.mls.lvl = cipso.smk_level; - smack_set_catset(cipso.smk_catset, nlsp); - } else { - nlsp->attr.mls.lvl = smack_cipso_direct; - smack_set_catset(smack, nlsp); - } - break; - default: - break; + rc = smack_to_cipso(smack, &cipso); + if (rc == 0) { + nlsp->attr.mls.lvl = cipso.smk_level; + smack_set_catset(cipso.smk_catset, nlsp); + } else { + nlsp->attr.mls.lvl = smack_cipso_direct; + smack_set_catset(smack, nlsp); } } /** * smack_netlabel - Set the secattr on a socket * @sk: the socket + * @labeled: socket label scheme * * Convert the outbound smack value (smk_out) to a * secattr and attach it to the socket. * * Returns 0 on success or an error code */ -static int smack_netlabel(struct sock *sk) +static int smack_netlabel(struct sock *sk, int labeled) { struct socket_smack *ssp; struct netlbl_lsm_secattr secattr; - int rc; + int rc = 0; ssp = sk->sk_security; - netlbl_secattr_init(&secattr); - smack_to_secattr(ssp->smk_out, &secattr); - rc = netlbl_sock_setattr(sk, &secattr); - netlbl_secattr_destroy(&secattr); + /* + * Usually the netlabel code will handle changing the + * packet labeling based on the label. + * The case of a single label host is different, because + * a single label host should never get a labeled packet + * even though the label is usually associated with a packet + * label. + */ + local_bh_disable(); + bh_lock_sock_nested(sk); + + if (ssp->smk_out == smack_net_ambient || + labeled == SMACK_UNLABELED_SOCKET) + netlbl_sock_delattr(sk); + else { + netlbl_secattr_init(&secattr); + smack_to_secattr(ssp->smk_out, &secattr); + rc = netlbl_sock_setattr(sk, &secattr); + netlbl_secattr_destroy(&secattr); + } + + bh_unlock_sock(sk); + local_bh_enable(); + /* + * Remember the label scheme used so that it is not + * necessary to do the netlabel setting if it has not + * changed the next time through. + * + * The -EDESTADDRREQ case is an indication that there's + * a single level host involved. + */ + if (rc == 0) + ssp->smk_labeled = labeled; return rc; } @@ -1432,7 +1457,7 @@ static int smack_inode_setsecurity(struct inode *inode, const char *name, ssp->smk_in = sp; else if (strcmp(name, XATTR_SMACK_IPOUT) == 0) { ssp->smk_out = sp; - rc = smack_netlabel(sock->sk); + rc = smack_netlabel(sock->sk, SMACK_CIPSO_SOCKET); if (rc != 0) printk(KERN_WARNING "Smack: \"%s\" netlbl error %d.\n", __func__, -rc); @@ -1462,7 +1487,108 @@ static int smack_socket_post_create(struct socket *sock, int family, /* * Set the outbound netlbl. */ - return smack_netlabel(sock->sk); + return smack_netlabel(sock->sk, SMACK_CIPSO_SOCKET); +} + + +/** + * smack_host_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. Because of the masking + * it cannot shortcut out on the first match. There are + * numerious ways to address the problem, but none of them + * have been applied here. + * + * Returns the label of the far end or NULL if it's not special. + */ +static char *smack_host_label(struct sockaddr_in *sip) +{ + struct smk_netlbladdr *snp; + char *bestlabel = NULL; + struct in_addr *siap = &sip->sin_addr; + struct in_addr *liap; + struct in_addr *miap; + struct in_addr bestmask; + + if (siap->s_addr == 0) + return NULL; + + bestmask.s_addr = 0; + + for (snp = smack_netlbladdrs; snp != NULL; snp = snp->smk_next) { + liap = &snp->smk_host.sin_addr; + miap = &snp->smk_mask; + /* + * If the addresses match after applying the list entry mask + * the entry matches the address. If it doesn't move along to + * the next entry. + */ + if ((liap->s_addr & miap->s_addr) != + (siap->s_addr & miap->s_addr)) + continue; + /* + * If the list entry mask identifies a single address + * it can't get any more specific. + */ + if (miap->s_addr == 0xffffffff) + return snp->smk_label; + /* + * If the list entry mask is less specific than the best + * already found this entry is uninteresting. + */ + if ((miap->s_addr | bestmask.s_addr) == bestmask.s_addr) + continue; + /* + * This is better than any entry found so far. + */ + bestmask.s_addr = miap->s_addr; + bestlabel = snp->smk_label; + } + + return bestlabel; +} + +/** + * smack_socket_connect - connect access check + * @sock: the socket + * @sap: the other end + * @addrlen: size of sap + * + * Verifies that a connection may be possible + * + * Returns 0 on success, and error code otherwise + */ +static int smack_socket_connect(struct socket *sock, struct sockaddr *sap, + int addrlen) +{ + struct socket_smack *ssp = sock->sk->sk_security; + char *hostsp; + int rc; + + if (sock->sk == NULL || sock->sk->sk_family != PF_INET) + return 0; + + if (addrlen < sizeof(struct sockaddr_in)) + return -EINVAL; + + hostsp = smack_host_label((struct sockaddr_in *)sap); + if (hostsp == NULL) { + if (ssp->smk_labeled != SMACK_CIPSO_SOCKET) + return smack_netlabel(sock->sk, SMACK_CIPSO_SOCKET); + return 0; + } + + rc = smk_access(ssp->smk_out, hostsp, MAY_WRITE); + if (rc != 0) + return rc; + + if (ssp->smk_labeled != SMACK_UNLABELED_SOCKET) + return smack_netlabel(sock->sk, SMACK_UNLABELED_SOCKET); + return 0; } /** @@ -2101,8 +2227,14 @@ static int smack_setprocattr(struct task_struct *p, char *name, if (newsmack == NULL) return -EINVAL; + /* + * No process is ever allowed the web ("@") label. + */ + if (newsmack == smack_known_web.smk_known) + return -EPERM; + new = prepare_creds(); - if (!new) + if (new == NULL) return -ENOMEM; new->security = newsmack; commit_creds(new); @@ -2144,6 +2276,49 @@ static int smack_unix_may_send(struct socket *sock, struct socket *other) } /** + * smack_socket_sendmsg - Smack check based on destination host + * @sock: the socket + * @msghdr: the message + * @size: the size of the message + * + * Return 0 if the current subject can write to the destination + * host. This is only a question if the destination is a single + * label host. + */ +static int smack_socket_sendmsg(struct socket *sock, struct msghdr *msg, + int size) +{ + struct sockaddr_in *sip = (struct sockaddr_in *) msg->msg_name; + struct socket_smack *ssp = sock->sk->sk_security; + char *hostsp; + int rc; + + /* + * Perfectly reasonable for this to be NULL + */ + if (sip == NULL || sip->sin_family != PF_INET) + return 0; + + hostsp = smack_host_label(sip); + if (hostsp == NULL) { + if (ssp->smk_labeled != SMACK_CIPSO_SOCKET) + return smack_netlabel(sock->sk, SMACK_CIPSO_SOCKET); + return 0; + } + + rc = smk_access(ssp->smk_out, hostsp, MAY_WRITE); + if (rc != 0) + return rc; + + if (ssp->smk_labeled != SMACK_UNLABELED_SOCKET) + return smack_netlabel(sock->sk, SMACK_UNLABELED_SOCKET); + + return 0; + +} + + +/** * smack_from_secattr - Convert a netlabel attr.mls.lvl/attr.mls.cat * pair to smack * @sap: netlabel secattr @@ -2154,44 +2329,66 @@ static int smack_unix_may_send(struct socket *sock, struct socket *other) static void smack_from_secattr(struct netlbl_lsm_secattr *sap, char *sip) { char smack[SMK_LABELLEN]; + char *sp; int pcat; - if ((sap->flags & NETLBL_SECATTR_MLS_LVL) == 0) { + if ((sap->flags & NETLBL_SECATTR_MLS_LVL) != 0) { /* + * Looks like a CIPSO packet. * If there are flags but no level netlabel isn't * behaving the way we expect it to. * + * Get the categories, if any * Without guidance regarding the smack value * for the packet fall back on the network * ambient value. */ - strncpy(sip, smack_net_ambient, SMK_MAXLEN); + memset(smack, '\0', SMK_LABELLEN); + if ((sap->flags & NETLBL_SECATTR_MLS_CAT) != 0) + for (pcat = -1;;) { + pcat = netlbl_secattr_catmap_walk( + sap->attr.mls.cat, pcat + 1); + if (pcat < 0) + break; + smack_catset_bit(pcat, smack); + } + /* + * If it is CIPSO using smack direct mapping + * we are already done. WeeHee. + */ + if (sap->attr.mls.lvl == smack_cipso_direct) { + memcpy(sip, smack, SMK_MAXLEN); + return; + } + /* + * Look it up in the supplied table if it is not + * a direct mapping. + */ + smack_from_cipso(sap->attr.mls.lvl, smack, sip); return; } - /* - * Get the categories, if any - */ - memset(smack, '\0', SMK_LABELLEN); - if ((sap->flags & NETLBL_SECATTR_MLS_CAT) != 0) - for (pcat = -1;;) { - pcat = netlbl_secattr_catmap_walk(sap->attr.mls.cat, - pcat + 1); - if (pcat < 0) - break; - smack_catset_bit(pcat, smack); - } - /* - * If it is CIPSO using smack direct mapping - * we are already done. WeeHee. - */ - if (sap->attr.mls.lvl == smack_cipso_direct) { - memcpy(sip, smack, SMK_MAXLEN); + if ((sap->flags & NETLBL_SECATTR_SECID) != 0) { + /* + * Looks like a fallback, which gives us a secid. + */ + sp = smack_from_secid(sap->attr.secid); + /* + * This has got to be a bug because it is + * impossible to specify a fallback without + * specifying the label, which will ensure + * it has a secid, and the only way to get a + * secid is from a fallback. + */ + BUG_ON(sp == NULL); + strncpy(sip, sp, SMK_MAXLEN); return; } /* - * Look it up in the supplied table if it is not a direct mapping. + * Without guidance regarding the smack value + * for the packet fall back on the network + * ambient value. */ - smack_from_cipso(sap->attr.mls.lvl, smack, sip); + strncpy(sip, smack_net_ambient, SMK_MAXLEN); return; } @@ -2207,6 +2404,7 @@ static int smack_socket_sock_rcv_skb(struct sock *sk, struct sk_buff *skb) struct netlbl_lsm_secattr secattr; struct socket_smack *ssp = sk->sk_security; char smack[SMK_LABELLEN]; + char *csp; int rc; if (sk->sk_family != PF_INET && sk->sk_family != PF_INET6) @@ -2215,21 +2413,24 @@ static int smack_socket_sock_rcv_skb(struct sock *sk, struct sk_buff *skb) /* * Translate what netlabel gave us. */ - memset(smack, '\0', SMK_LABELLEN); netlbl_secattr_init(&secattr); + rc = netlbl_skbuff_getattr(skb, sk->sk_family, &secattr); - if (rc == 0) + if (rc == 0) { smack_from_secattr(&secattr, smack); - else - strncpy(smack, smack_net_ambient, SMK_MAXLEN); + csp = smack; + } else + csp = smack_net_ambient; + netlbl_secattr_destroy(&secattr); + /* * Receiving a packet requires that the other end * be able to write here. Read access is not required. * This is the simplist possible security model * for networking. */ - rc = smk_access(smack, ssp->smk_in, MAY_WRITE); + rc = smk_access(csp, ssp->smk_in, MAY_WRITE); if (rc != 0) netlbl_skbuff_err(skb, rc, 0); return rc; @@ -2298,7 +2499,6 @@ static int smack_socket_getpeersec_dgram(struct socket *sock, /* * Translate what netlabel gave us. */ - memset(smack, '\0', SMK_LABELLEN); netlbl_secattr_init(&secattr); rc = netlbl_skbuff_getattr(skb, family, &secattr); if (rc == 0) @@ -2341,7 +2541,7 @@ static void smack_sock_graft(struct sock *sk, struct socket *parent) ssp->smk_in = ssp->smk_out = current_security(); ssp->smk_packet[0] = '\0'; - rc = smack_netlabel(sk); + rc = smack_netlabel(sk, SMACK_CIPSO_SOCKET); if (rc != 0) printk(KERN_WARNING "Smack: \"%s\" netlbl error %d.\n", __func__, -rc); @@ -2367,7 +2567,6 @@ static int smack_inet_conn_request(struct sock *sk, struct sk_buff *skb, if (skb == NULL) return -EACCES; - memset(smack, '\0', SMK_LABELLEN); netlbl_secattr_init(&skb_secattr); rc = netlbl_skbuff_getattr(skb, sk->sk_family, &skb_secattr); if (rc == 0) @@ -2732,6 +2931,8 @@ struct security_operations smack_ops = { .unix_may_send = smack_unix_may_send, .socket_post_create = smack_socket_post_create, + .socket_connect = smack_socket_connect, + .socket_sendmsg = smack_socket_sendmsg, .socket_sock_rcv_skb = smack_socket_sock_rcv_skb, .socket_getpeersec_stream = smack_socket_getpeersec_stream, .socket_getpeersec_dgram = smack_socket_getpeersec_dgram, @@ -2783,7 +2984,6 @@ static __init int smack_init(void) /* * Initialize locks */ - spin_lock_init(&smack_known_unset.smk_cipsolock); spin_lock_init(&smack_known_huh.smk_cipsolock); spin_lock_init(&smack_known_hat.smk_cipsolock); spin_lock_init(&smack_known_star.smk_cipsolock); diff --git a/security/smack/smackfs.c b/security/smack/smackfs.c index 247dc9ebbc71..bf107a389ac1 100644 --- a/security/smack/smackfs.c +++ b/security/smack/smackfs.c @@ -20,6 +20,7 @@ #include <linux/vmalloc.h> #include <linux/security.h> #include <linux/mutex.h> +#include <net/net_namespace.h> #include <net/netlabel.h> #include <net/cipso_ipv4.h> #include <linux/seq_file.h> @@ -38,7 +39,7 @@ enum smk_inos { SMK_DOI = 5, /* CIPSO DOI */ SMK_DIRECT = 6, /* CIPSO level indicating direct label */ SMK_AMBIENT = 7, /* internet ambient label */ - SMK_NLTYPE = 8, /* label scheme to use by default */ + SMK_NETLBLADDR = 8, /* single label hosts */ SMK_ONLYCAP = 9, /* the only "capable" label */ }; @@ -48,6 +49,7 @@ enum smk_inos { static DEFINE_MUTEX(smack_list_lock); static DEFINE_MUTEX(smack_cipso_lock); static DEFINE_MUTEX(smack_ambient_lock); +static DEFINE_MUTEX(smk_netlbladdr_lock); /* * This is the "ambient" label for network traffic. @@ -57,12 +59,6 @@ static DEFINE_MUTEX(smack_ambient_lock); char *smack_net_ambient = smack_known_floor.smk_known; /* - * This is the default packet marking scheme for network traffic. - * It can be reset via smackfs/nltype - */ -int smack_net_nltype = NETLBL_NLTYPE_CIPSOV4; - -/* * This is the level in a CIPSO header that indicates a * smack label is contained directly in the category set. * It can be reset via smackfs/direct @@ -79,6 +75,13 @@ int smack_cipso_direct = SMACK_CIPSO_DIRECT_DEFAULT; */ char *smack_onlycap; +/* + * Certain IP addresses may be designated as single label hosts. + * Packets are sent there unlabeled, but only from tasks that + * can write to the specified label. + */ +struct smk_netlbladdr *smack_netlbladdrs; + static int smk_cipso_doi_value = SMACK_CIPSO_DOI_DEFAULT; struct smk_list_entry *smack_list; @@ -104,6 +107,24 @@ struct smk_list_entry *smack_list; #define SMK_ACCESSLEN (sizeof(SMK_ACCESS) - 1) #define SMK_LOADLEN (SMK_LABELLEN + SMK_LABELLEN + SMK_ACCESSLEN) +/** + * smk_netlabel_audit_set - fill a netlbl_audit struct + * @nap: structure to fill + */ +static void smk_netlabel_audit_set(struct netlbl_audit *nap) +{ + nap->loginuid = audit_get_loginuid(current); + nap->sessionid = audit_get_sessionid(current); + nap->secid = smack_to_secid(current_security()); +} + +/* + * Values for parsing single label host rules + * "1.2.3.4 X" + * "192.168.138.129/32 abcdefghijklmnopqrstuvw" + */ +#define SMK_NETLBLADDRMIN 9 +#define SMK_NETLBLADDRMAX 42 /* * Seq_file read operations for /smack/load @@ -344,13 +365,11 @@ static void smk_cipso_doi(void) { int rc; struct cipso_v4_doi *doip; - struct netlbl_audit audit_info; + struct netlbl_audit nai; - audit_info.loginuid = audit_get_loginuid(current); - audit_info.sessionid = audit_get_sessionid(current); - audit_info.secid = smack_to_secid(current_security()); + smk_netlabel_audit_set(&nai); - rc = netlbl_cfg_map_del(NULL, &audit_info); + rc = netlbl_cfg_map_del(NULL, PF_INET, NULL, NULL, &nai); if (rc != 0) printk(KERN_WARNING "%s:%d remove rc = %d\n", __func__, __LINE__, rc); @@ -365,11 +384,19 @@ static void smk_cipso_doi(void) for (rc = 1; rc < CIPSO_V4_TAG_MAXCNT; rc++) doip->tags[rc] = CIPSO_V4_TAG_INVALID; - rc = netlbl_cfg_cipsov4_add_map(doip, NULL, &audit_info); + rc = netlbl_cfg_cipsov4_add(doip, &nai); if (rc != 0) { - printk(KERN_WARNING "%s:%d add rc = %d\n", + printk(KERN_WARNING "%s:%d cipso add rc = %d\n", __func__, __LINE__, rc); kfree(doip); + return; + } + rc = netlbl_cfg_cipsov4_map_add(doip->doi, NULL, NULL, NULL, &nai); + if (rc != 0) { + printk(KERN_WARNING "%s:%d map add rc = %d\n", + __func__, __LINE__, rc); + kfree(doip); + return; } } @@ -379,20 +406,19 @@ static void smk_cipso_doi(void) static void smk_unlbl_ambient(char *oldambient) { int rc; - struct netlbl_audit audit_info; + struct netlbl_audit nai; - audit_info.loginuid = audit_get_loginuid(current); - audit_info.sessionid = audit_get_sessionid(current); - audit_info.secid = smack_to_secid(current_security()); + smk_netlabel_audit_set(&nai); if (oldambient != NULL) { - rc = netlbl_cfg_map_del(oldambient, &audit_info); + rc = netlbl_cfg_map_del(oldambient, PF_INET, NULL, NULL, &nai); if (rc != 0) printk(KERN_WARNING "%s:%d remove rc = %d\n", __func__, __LINE__, rc); } - rc = netlbl_cfg_unlbl_add_map(smack_net_ambient, &audit_info); + rc = netlbl_cfg_unlbl_map_add(smack_net_ambient, PF_INET, + NULL, NULL, &nai); if (rc != 0) printk(KERN_WARNING "%s:%d add rc = %d\n", __func__, __LINE__, rc); @@ -603,6 +629,201 @@ static const struct file_operations smk_cipso_ops = { .release = seq_release, }; +/* + * Seq_file read operations for /smack/netlabel + */ + +static void *netlbladdr_seq_start(struct seq_file *s, loff_t *pos) +{ + if (*pos == SEQ_READ_FINISHED) + return NULL; + + return smack_netlbladdrs; +} + +static void *netlbladdr_seq_next(struct seq_file *s, void *v, loff_t *pos) +{ + struct smk_netlbladdr *skp = ((struct smk_netlbladdr *) v)->smk_next; + + if (skp == NULL) + *pos = SEQ_READ_FINISHED; + + return skp; +} +/* +#define BEMASK 0x80000000 +*/ +#define BEMASK 0x00000001 +#define BEBITS (sizeof(__be32) * 8) + +/* + * Print host/label pairs + */ +static int netlbladdr_seq_show(struct seq_file *s, void *v) +{ + struct smk_netlbladdr *skp = (struct smk_netlbladdr *) v; + unsigned char *hp = (char *) &skp->smk_host.sin_addr.s_addr; + __be32 bebits; + int maskn = 0; + + for (bebits = BEMASK; bebits != 0; maskn++, bebits <<= 1) + if ((skp->smk_mask.s_addr & bebits) == 0) + break; + + seq_printf(s, "%u.%u.%u.%u/%d %s\n", + hp[0], hp[1], hp[2], hp[3], maskn, skp->smk_label); + + return 0; +} + +static void netlbladdr_seq_stop(struct seq_file *s, void *v) +{ + /* No-op */ +} + +static struct seq_operations netlbladdr_seq_ops = { + .start = netlbladdr_seq_start, + .stop = netlbladdr_seq_stop, + .next = netlbladdr_seq_next, + .show = netlbladdr_seq_show, +}; + +/** + * smk_open_netlbladdr - open() for /smack/netlabel + * @inode: inode structure representing file + * @file: "netlabel" file pointer + * + * Connect our netlbladdr_seq_* operations with /smack/netlabel + * file_operations + */ +static int smk_open_netlbladdr(struct inode *inode, struct file *file) +{ + return seq_open(file, &netlbladdr_seq_ops); +} + +/** + * smk_write_netlbladdr - write() for /smack/netlabel + * @filp: file pointer, not actually used + * @buf: where to get the data from + * @count: bytes sent + * @ppos: where to start + * + * Accepts only one netlbladdr per write call. + * Returns number of bytes written or error code, as appropriate + */ +static ssize_t smk_write_netlbladdr(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + struct smk_netlbladdr *skp; + struct sockaddr_in newname; + char smack[SMK_LABELLEN]; + char *sp; + char data[SMK_NETLBLADDRMAX]; + char *host = (char *)&newname.sin_addr.s_addr; + int rc; + struct netlbl_audit audit_info; + struct in_addr mask; + unsigned int m; + __be32 bebits = BEMASK; + __be32 nsa; + + /* + * Must have privilege. + * No partial writes. + * Enough data must be present. + * "<addr/mask, as a.b.c.d/e><space><label>" + * "<addr, as a.b.c.d><space><label>" + */ + if (!capable(CAP_MAC_ADMIN)) + return -EPERM; + if (*ppos != 0) + return -EINVAL; + if (count < SMK_NETLBLADDRMIN || count > SMK_NETLBLADDRMAX) + return -EINVAL; + if (copy_from_user(data, buf, count) != 0) + return -EFAULT; + + data[count] = '\0'; + + rc = sscanf(data, "%hhd.%hhd.%hhd.%hhd/%d %s", + &host[0], &host[1], &host[2], &host[3], &m, smack); + if (rc != 6) { + rc = sscanf(data, "%hhd.%hhd.%hhd.%hhd %s", + &host[0], &host[1], &host[2], &host[3], smack); + if (rc != 5) + return -EINVAL; + m = BEBITS; + } + if (m > BEBITS) + return -EINVAL; + + sp = smk_import(smack, 0); + if (sp == NULL) + return -EINVAL; + + for (mask.s_addr = 0; m > 0; m--) { + mask.s_addr |= bebits; + bebits <<= 1; + } + /* + * Only allow one writer at a time. Writes should be + * quite rare and small in any case. + */ + mutex_lock(&smk_netlbladdr_lock); + + nsa = newname.sin_addr.s_addr; + for (skp = smack_netlbladdrs; skp != NULL; skp = skp->smk_next) + if (skp->smk_host.sin_addr.s_addr == nsa && + skp->smk_mask.s_addr == mask.s_addr) + break; + + smk_netlabel_audit_set(&audit_info); + + if (skp == NULL) { + skp = kzalloc(sizeof(*skp), GFP_KERNEL); + if (skp == NULL) + rc = -ENOMEM; + else { + rc = 0; + skp->smk_host.sin_addr.s_addr = newname.sin_addr.s_addr; + skp->smk_mask.s_addr = mask.s_addr; + skp->smk_next = smack_netlbladdrs; + skp->smk_label = sp; + smack_netlbladdrs = skp; + } + } else { + rc = netlbl_cfg_unlbl_static_del(&init_net, NULL, + &skp->smk_host.sin_addr, &skp->smk_mask, + PF_INET, &audit_info); + skp->smk_label = sp; + } + + /* + * Now tell netlabel about the single label nature of + * this host so that incoming packets get labeled. + */ + + if (rc == 0) + rc = netlbl_cfg_unlbl_static_add(&init_net, NULL, + &skp->smk_host.sin_addr, &skp->smk_mask, PF_INET, + smack_to_secid(skp->smk_label), &audit_info); + + if (rc == 0) + rc = count; + + mutex_unlock(&smk_netlbladdr_lock); + + return rc; +} + +static const struct file_operations smk_netlbladdr_ops = { + .open = smk_open_netlbladdr, + .read = seq_read, + .llseek = seq_lseek, + .write = smk_write_netlbladdr, + .release = seq_release, +}; + /** * smk_read_doi - read() for /smack/doi * @filp: file pointer, not actually used @@ -891,110 +1112,6 @@ static const struct file_operations smk_onlycap_ops = { .write = smk_write_onlycap, }; -struct option_names { - int o_number; - char *o_name; - char *o_alias; -}; - -static struct option_names netlbl_choices[] = { - { NETLBL_NLTYPE_RIPSO, - NETLBL_NLTYPE_RIPSO_NAME, "ripso" }, - { NETLBL_NLTYPE_CIPSOV4, - NETLBL_NLTYPE_CIPSOV4_NAME, "cipsov4" }, - { NETLBL_NLTYPE_CIPSOV4, - NETLBL_NLTYPE_CIPSOV4_NAME, "cipso" }, - { NETLBL_NLTYPE_CIPSOV6, - NETLBL_NLTYPE_CIPSOV6_NAME, "cipsov6" }, - { NETLBL_NLTYPE_UNLABELED, - NETLBL_NLTYPE_UNLABELED_NAME, "unlabeled" }, -}; - -/** - * smk_read_nltype - read() for /smack/nltype - * @filp: file pointer, not actually used - * @buf: where to put the result - * @count: maximum to send along - * @ppos: where to start - * - * Returns number of bytes read or error code, as appropriate - */ -static ssize_t smk_read_nltype(struct file *filp, char __user *buf, - size_t count, loff_t *ppos) -{ - char bound[40]; - ssize_t rc; - int i; - - if (count < SMK_LABELLEN) - return -EINVAL; - - if (*ppos != 0) - return 0; - - sprintf(bound, "unknown"); - - for (i = 0; i < ARRAY_SIZE(netlbl_choices); i++) - if (smack_net_nltype == netlbl_choices[i].o_number) { - sprintf(bound, "%s", netlbl_choices[i].o_name); - break; - } - - rc = simple_read_from_buffer(buf, count, ppos, bound, strlen(bound)); - - return rc; -} - -/** - * smk_write_nltype - write() for /smack/nltype - * @filp: file pointer, not actually used - * @buf: where to get the data from - * @count: bytes sent - * @ppos: where to start - * - * Returns number of bytes written or error code, as appropriate - */ -static ssize_t smk_write_nltype(struct file *file, const char __user *buf, - size_t count, loff_t *ppos) -{ - char bound[40]; - char *cp; - int i; - - if (!capable(CAP_MAC_ADMIN)) - return -EPERM; - - if (count >= 40) - return -EINVAL; - - if (copy_from_user(bound, buf, count) != 0) - return -EFAULT; - - bound[count] = '\0'; - cp = strchr(bound, ' '); - if (cp != NULL) - *cp = '\0'; - cp = strchr(bound, '\n'); - if (cp != NULL) - *cp = '\0'; - - for (i = 0; i < ARRAY_SIZE(netlbl_choices); i++) - if (strcmp(bound, netlbl_choices[i].o_name) == 0 || - strcmp(bound, netlbl_choices[i].o_alias) == 0) { - smack_net_nltype = netlbl_choices[i].o_number; - return count; - } - /* - * Not a valid choice. - */ - return -EINVAL; -} - -static const struct file_operations smk_nltype_ops = { - .read = smk_read_nltype, - .write = smk_write_nltype, -}; - /** * smk_fill_super - fill the /smackfs superblock * @sb: the empty superblock @@ -1021,8 +1138,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_NLTYPE] = - {"nltype", &smk_nltype_ops, S_IRUGO|S_IWUSR}, + [SMK_NETLBLADDR] = + {"netlabel", &smk_netlbladdr_ops, S_IRUGO|S_IWUSR}, [SMK_ONLYCAP] = {"onlycap", &smk_onlycap_ops, S_IRUGO|S_IWUSR}, /* last one */ {""} |