diff options
author | Paul Moore <paul.moore@hp.com> | 2008-01-29 08:38:23 -0500 |
---|---|---|
committer | James Morris <jmorris@namei.org> | 2008-01-30 08:17:25 +1100 |
commit | 220deb966ea51e0dedb6a187c0763120809f3e64 (patch) | |
tree | 7d0e5dd8048907c364b4eeff294991937b466c7e /security | |
parent | f67f4f315f31e7907779adb3296fb6682e755342 (diff) | |
download | linux-220deb966ea51e0dedb6a187c0763120809f3e64.tar.bz2 |
SELinux: Better integration between peer labeling subsystems
Rework the handling of network peer labels so that the different peer labeling
subsystems work better together. This includes moving both subsystems to a
single "peer" object class which involves not only changes to the permission
checks but an improved method of consolidating multiple packet peer labels.
As part of this work the inbound packet permission check code has been heavily
modified to handle both the old and new behavior in as sane a fashion as
possible.
Signed-off-by: Paul Moore <paul.moore@hp.com>
Signed-off-by: James Morris <jmorris@namei.org>
Diffstat (limited to 'security')
-rw-r--r-- | security/selinux/hooks.c | 204 | ||||
-rw-r--r-- | security/selinux/include/netlabel.h | 3 | ||||
-rw-r--r-- | security/selinux/include/objsec.h | 2 | ||||
-rw-r--r-- | security/selinux/include/security.h | 4 | ||||
-rw-r--r-- | security/selinux/netlabel.c | 10 | ||||
-rw-r--r-- | security/selinux/ss/services.c | 85 |
6 files changed, 208 insertions, 100 deletions
diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 4bca0af4f2af..bfe9a05db3a2 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -50,6 +50,7 @@ #include <net/icmp.h> #include <net/ip.h> /* for local_port_range[] */ #include <net/tcp.h> /* struct or_callable used in sock_rcv_skb */ +#include <net/net_namespace.h> #include <asm/uaccess.h> #include <asm/ioctls.h> #include <linux/bitops.h> @@ -3426,36 +3427,39 @@ static int selinux_parse_skb(struct sk_buff *skb, struct avc_audit_data *ad, } /** - * selinux_skb_extlbl_sid - Determine the external label of a packet + * selinux_skb_peerlbl_sid - Determine the peer label of a packet * @skb: the packet * @family: protocol family - * @sid: the packet's SID + * @sid: the packet's peer label SID * * Description: - * Check the various different forms of external packet labeling and determine - * the external SID for the packet. If only one form of external labeling is - * present then it is used, if both labeled IPsec and NetLabel labels are - * present then the SELinux type information is taken from the labeled IPsec - * SA and the MLS sensitivity label information is taken from the NetLabel - * security attributes. This bit of "magic" is done in the call to - * selinux_netlbl_skbuff_getsid(). + * Check the various different forms of network peer labeling and determine + * the peer label/SID for the packet; most of the magic actually occurs in + * the security server function security_net_peersid_cmp(). The function + * returns zero if the value in @sid is valid (although it may be SECSID_NULL) + * or -EACCES if @sid is invalid due to inconsistencies with the different + * peer labels. * */ -static void selinux_skb_extlbl_sid(struct sk_buff *skb, - u16 family, - u32 *sid) +static int selinux_skb_peerlbl_sid(struct sk_buff *skb, u16 family, u32 *sid) { u32 xfrm_sid; u32 nlbl_sid; + u32 nlbl_type; selinux_skb_xfrm_sid(skb, &xfrm_sid); - if (selinux_netlbl_skbuff_getsid(skb, - family, - (xfrm_sid == SECSID_NULL ? - SECINITSID_NETMSG : xfrm_sid), - &nlbl_sid) != 0) - nlbl_sid = SECSID_NULL; - *sid = (nlbl_sid == SECSID_NULL ? xfrm_sid : nlbl_sid); + selinux_netlbl_skbuff_getsid(skb, + family, + SECINITSID_NETMSG, + &nlbl_type, + &nlbl_sid); + + if (security_net_peersid_resolve(nlbl_sid, nlbl_type, + xfrm_sid, + sid) != 0) + return -EACCES; + + return 0; } /* socket security operations */ @@ -3521,6 +3525,7 @@ static int selinux_socket_post_create(struct socket *sock, int family, if (sock->sk) { sksec = sock->sk->sk_security; sksec->sid = isec->sid; + sksec->sclass = isec->sclass; err = selinux_netlbl_socket_post_create(sock); } @@ -3824,104 +3829,114 @@ static int selinux_socket_unix_may_send(struct socket *sock, return 0; } -static int selinux_sock_rcv_skb_compat(struct sock *sk, struct sk_buff *skb, - struct avc_audit_data *ad, - u16 family, char *addrp) +static int selinux_sock_rcv_skb_iptables_compat(struct sock *sk, + struct sk_buff *skb, + struct avc_audit_data *ad, + u16 family, + char *addrp) { - int err = 0; - u32 netif_perm, node_perm, node_sid, if_sid, recv_perm = 0; - struct socket *sock; - u16 sock_class = 0; - u32 sock_sid = 0; - - read_lock_bh(&sk->sk_callback_lock); - sock = sk->sk_socket; - if (sock) { - struct inode *inode; - inode = SOCK_INODE(sock); - if (inode) { - struct inode_security_struct *isec; - isec = inode->i_security; - sock_sid = isec->sid; - sock_class = isec->sclass; - } - } - read_unlock_bh(&sk->sk_callback_lock); - if (!sock_sid) - goto out; - - if (!skb->dev) - goto out; + int err; + struct sk_security_struct *sksec = sk->sk_security; + u16 sk_class; + u32 netif_perm, node_perm, recv_perm; + u32 port_sid, node_sid, if_sid, sk_sid; - err = sel_netif_sid(skb->iif, &if_sid); - if (err) - goto out; + sk_sid = sksec->sid; + sk_class = sksec->sclass; - switch (sock_class) { + switch (sk_class) { case SECCLASS_UDP_SOCKET: netif_perm = NETIF__UDP_RECV; node_perm = NODE__UDP_RECV; recv_perm = UDP_SOCKET__RECV_MSG; break; - case SECCLASS_TCP_SOCKET: netif_perm = NETIF__TCP_RECV; node_perm = NODE__TCP_RECV; recv_perm = TCP_SOCKET__RECV_MSG; break; - case SECCLASS_DCCP_SOCKET: netif_perm = NETIF__DCCP_RECV; node_perm = NODE__DCCP_RECV; recv_perm = DCCP_SOCKET__RECV_MSG; break; - default: netif_perm = NETIF__RAWIP_RECV; node_perm = NODE__RAWIP_RECV; + recv_perm = 0; break; } - err = avc_has_perm(sock_sid, if_sid, SECCLASS_NETIF, netif_perm, ad); + err = sel_netif_sid(skb->iif, &if_sid); if (err) - goto out; + return err; + err = avc_has_perm(sk_sid, if_sid, SECCLASS_NETIF, netif_perm, ad); + if (err) + return err; err = sel_netnode_sid(addrp, family, &node_sid); if (err) - goto out; - - err = avc_has_perm(sock_sid, node_sid, SECCLASS_NODE, node_perm, ad); + return err; + err = avc_has_perm(sk_sid, node_sid, SECCLASS_NODE, node_perm, ad); if (err) - goto out; + return err; - if (recv_perm) { - u32 port_sid; + if (!recv_perm) + return 0; + err = security_port_sid(sk->sk_family, sk->sk_type, + sk->sk_protocol, ntohs(ad->u.net.sport), + &port_sid); + if (err) + return err; + return avc_has_perm(sk_sid, port_sid, sk_class, recv_perm, ad); +} - err = security_port_sid(sk->sk_family, sk->sk_type, - sk->sk_protocol, ntohs(ad->u.net.sport), - &port_sid); - if (err) - goto out; +static int selinux_sock_rcv_skb_compat(struct sock *sk, struct sk_buff *skb, + struct avc_audit_data *ad, + u16 family, char *addrp) +{ + int err; + struct sk_security_struct *sksec = sk->sk_security; + u32 peer_sid; + u32 sk_sid = sksec->sid; + + if (selinux_compat_net) + err = selinux_sock_rcv_skb_iptables_compat(sk, skb, ad, + family, addrp); + else + err = avc_has_perm(sk_sid, skb->secmark, SECCLASS_PACKET, + PACKET__RECV, ad); + if (err) + return err; - err = avc_has_perm(sock_sid, port_sid, - sock_class, recv_perm, ad); + if (selinux_policycap_netpeer) { + err = selinux_skb_peerlbl_sid(skb, family, &peer_sid); + if (err) + return err; + err = avc_has_perm(sk_sid, peer_sid, + SECCLASS_PEER, PEER__RECV, ad); + } else { + err = selinux_netlbl_sock_rcv_skb(sksec, skb, family, ad); + if (err) + return err; + err = selinux_xfrm_sock_rcv_skb(sksec->sid, skb, ad); } -out: return err; } static int selinux_socket_sock_rcv_skb(struct sock *sk, struct sk_buff *skb) { - u16 family; - char *addrp; - int err = 0; - struct avc_audit_data ad; + int err; struct sk_security_struct *sksec = sk->sk_security; + u16 family = sk->sk_family; + u32 sk_sid = sksec->sid; + u32 peer_sid; + struct avc_audit_data ad; + char *addrp; - family = sk->sk_family; if (family != PF_INET && family != PF_INET6) - goto out; + return 0; /* Handle mapped IPv4 packets arriving via IPv6 sockets */ if (family == PF_INET6 && skb->protocol == htons(ETH_P_IP)) @@ -3930,26 +3945,27 @@ static int selinux_socket_sock_rcv_skb(struct sock *sk, struct sk_buff *skb) AVC_AUDIT_DATA_INIT(&ad, NET); ad.u.net.netif = skb->iif; ad.u.net.family = family; - err = selinux_parse_skb(skb, &ad, &addrp, 1, NULL); if (err) - goto out; + return err; - if (selinux_compat_net) - err = selinux_sock_rcv_skb_compat(sk, skb, &ad, family, addrp); - else - err = avc_has_perm(sksec->sid, skb->secmark, SECCLASS_PACKET, - PACKET__RECV, &ad); - if (err) - goto out; + /* If any sort of compatibility mode is enabled then handoff processing + * to the selinux_sock_rcv_skb_compat() function to deal with the + * special handling. We do this in an attempt to keep this function + * as fast and as clean as possible. */ + if (selinux_compat_net || !selinux_policycap_netpeer) + return selinux_sock_rcv_skb_compat(sk, skb, &ad, + family, addrp); - err = selinux_netlbl_sock_rcv_skb(sksec, skb, family, &ad); + err = avc_has_perm(sk_sid, skb->secmark, SECCLASS_PACKET, + PACKET__RECV, &ad); if (err) - goto out; + return err; - err = selinux_xfrm_sock_rcv_skb(sksec->sid, skb, &ad); -out: - return err; + err = selinux_skb_peerlbl_sid(skb, family, &peer_sid); + if (err) + return err; + return avc_has_perm(sk_sid, peer_sid, SECCLASS_PEER, PEER__RECV, &ad); } static int selinux_socket_getpeersec_stream(struct socket *sock, char __user *optval, @@ -4011,7 +4027,7 @@ static int selinux_socket_getpeersec_dgram(struct socket *sock, struct sk_buff * if (sock && family == PF_UNIX) selinux_get_inode_sid(SOCK_INODE(sock), &peer_secid); else if (skb) - selinux_skb_extlbl_sid(skb, family, &peer_secid); + selinux_skb_peerlbl_sid(skb, family, &peer_secid); out: *secid = peer_secid; @@ -4037,6 +4053,7 @@ static void selinux_sk_clone_security(const struct sock *sk, struct sock *newsk) newssec->sid = ssec->sid; newssec->peer_sid = ssec->peer_sid; + newssec->sclass = ssec->sclass; selinux_netlbl_sk_security_clone(ssec, newssec); } @@ -4060,6 +4077,7 @@ static void selinux_sock_graft(struct sock* sk, struct socket *parent) if (sk->sk_family == PF_INET || sk->sk_family == PF_INET6 || sk->sk_family == PF_UNIX) isec->sid = sksec->sid; + sksec->sclass = isec->sclass; selinux_netlbl_sock_graft(sk, parent); } @@ -4072,7 +4090,9 @@ static int selinux_inet_conn_request(struct sock *sk, struct sk_buff *skb, u32 newsid; u32 peersid; - selinux_skb_extlbl_sid(skb, sk->sk_family, &peersid); + err = selinux_skb_peerlbl_sid(skb, sk->sk_family, &peersid); + if (err) + return err; if (peersid == SECSID_NULL) { req->secid = sksec->sid; req->peer_secid = SECSID_NULL; @@ -4110,7 +4130,7 @@ static void selinux_inet_conn_established(struct sock *sk, { struct sk_security_struct *sksec = sk->sk_security; - selinux_skb_extlbl_sid(skb, sk->sk_family, &sksec->peer_sid); + selinux_skb_peerlbl_sid(skb, sk->sk_family, &sksec->peer_sid); } static void selinux_req_classify_flow(const struct request_sock *req, diff --git a/security/selinux/include/netlabel.h b/security/selinux/include/netlabel.h index 272769a1cb96..c8c05a6f298c 100644 --- a/security/selinux/include/netlabel.h +++ b/security/selinux/include/netlabel.h @@ -49,6 +49,7 @@ void selinux_netlbl_sk_security_clone(struct sk_security_struct *ssec, int selinux_netlbl_skbuff_getsid(struct sk_buff *skb, u16 family, u32 base_sid, + u32 *type, u32 *sid); void selinux_netlbl_sock_graft(struct sock *sk, struct socket *sock); @@ -89,8 +90,10 @@ static inline void selinux_netlbl_sk_security_clone( static inline int selinux_netlbl_skbuff_getsid(struct sk_buff *skb, u16 family, u32 base_sid, + u32 *type, u32 *sid) { + *type = NETLBL_NLTYPE_NONE; *sid = SECSID_NULL; return 0; } diff --git a/security/selinux/include/objsec.h b/security/selinux/include/objsec.h index 95fb5ec17354..c6c2bb4ebacc 100644 --- a/security/selinux/include/objsec.h +++ b/security/selinux/include/objsec.h @@ -113,8 +113,8 @@ struct sk_security_struct { struct sock *sk; /* back pointer to sk object */ u32 sid; /* SID of this object */ u32 peer_sid; /* SID of peer */ -#ifdef CONFIG_NETLABEL u16 sclass; /* sock security class */ +#ifdef CONFIG_NETLABEL enum { /* NetLabel state */ NLBL_UNSET = 0, NLBL_REQUIRE, diff --git a/security/selinux/include/security.h b/security/selinux/include/security.h index a22de9771806..9347e2daa8d4 100644 --- a/security/selinux/include/security.h +++ b/security/selinux/include/security.h @@ -99,6 +99,10 @@ int security_validate_transition(u32 oldsid, u32 newsid, u32 tasksid, int security_sid_mls_copy(u32 sid, u32 mls_sid, u32 *new_sid); +int security_net_peersid_resolve(u32 nlbl_sid, u32 nlbl_type, + u32 xfrm_sid, + u32 *peer_sid); + int security_get_classes(char ***classes, int *nclasses); int security_get_permissions(char *class, char ***perms, int *nperms); int security_get_reject_unknown(void); diff --git a/security/selinux/netlabel.c b/security/selinux/netlabel.c index f4bcbf12a4c9..b54d28fd3b5d 100644 --- a/security/selinux/netlabel.c +++ b/security/selinux/netlabel.c @@ -137,7 +137,6 @@ void selinux_netlbl_sk_security_clone(struct sk_security_struct *ssec, * lock as other threads could have access to ssec */ rcu_read_lock(); selinux_netlbl_sk_security_reset(newssec, ssec->sk->sk_family); - newssec->sclass = ssec->sclass; rcu_read_unlock(); } @@ -146,6 +145,7 @@ void selinux_netlbl_sk_security_clone(struct sk_security_struct *ssec, * @skb: the packet * @family: protocol family * @base_sid: the SELinux SID to use as a context for MLS only attributes + * @type: NetLabel labeling protocol type * @sid: the SID * * Description: @@ -157,6 +157,7 @@ void selinux_netlbl_sk_security_clone(struct sk_security_struct *ssec, int selinux_netlbl_skbuff_getsid(struct sk_buff *skb, u16 family, u32 base_sid, + u32 *type, u32 *sid) { int rc; @@ -177,6 +178,7 @@ int selinux_netlbl_skbuff_getsid(struct sk_buff *skb, netlbl_cache_add(skb, &secattr); } else *sid = SECSID_NULL; + *type = secattr.type; netlbl_secattr_destroy(&secattr); return rc; @@ -194,13 +196,10 @@ int selinux_netlbl_skbuff_getsid(struct sk_buff *skb, */ void selinux_netlbl_sock_graft(struct sock *sk, struct socket *sock) { - struct inode_security_struct *isec = SOCK_INODE(sock)->i_security; struct sk_security_struct *sksec = sk->sk_security; struct netlbl_lsm_secattr secattr; u32 nlbl_peer_sid; - sksec->sclass = isec->sclass; - rcu_read_lock(); if (sksec->nlbl_state != NLBL_REQUIRE) { @@ -238,11 +237,8 @@ int selinux_netlbl_socket_post_create(struct socket *sock) { int rc = 0; struct sock *sk = sock->sk; - struct inode_security_struct *isec = SOCK_INODE(sock)->i_security; struct sk_security_struct *sksec = sk->sk_security; - sksec->sclass = isec->sclass; - rcu_read_lock(); if (sksec->nlbl_state == NLBL_REQUIRE) rc = selinux_netlbl_sock_setsid(sk, sksec->sid); diff --git a/security/selinux/ss/services.c b/security/selinux/ss/services.c index 8ee04a424df7..7f0ee1b91e1d 100644 --- a/security/selinux/ss/services.c +++ b/security/selinux/ss/services.c @@ -2054,6 +2054,91 @@ out: return rc; } +/** + * security_net_peersid_resolve - Compare and resolve two network peer SIDs + * @nlbl_sid: NetLabel SID + * @nlbl_type: NetLabel labeling protocol type + * @xfrm_sid: XFRM SID + * + * Description: + * Compare the @nlbl_sid and @xfrm_sid values and if the two SIDs can be + * resolved into a single SID it is returned via @peer_sid and the function + * returns zero. Otherwise @peer_sid is set to SECSID_NULL and the function + * returns a negative value. A table summarizing the behavior is below: + * + * | function return | @sid + * ------------------------------+-----------------+----------------- + * no peer labels | 0 | SECSID_NULL + * single peer label | 0 | <peer_label> + * multiple, consistent labels | 0 | <peer_label> + * multiple, inconsistent labels | -<errno> | SECSID_NULL + * + */ +int security_net_peersid_resolve(u32 nlbl_sid, u32 nlbl_type, + u32 xfrm_sid, + u32 *peer_sid) +{ + int rc; + struct context *nlbl_ctx; + struct context *xfrm_ctx; + + /* handle the common (which also happens to be the set of easy) cases + * right away, these two if statements catch everything involving a + * single or absent peer SID/label */ + if (xfrm_sid == SECSID_NULL) { + *peer_sid = nlbl_sid; + return 0; + } + /* NOTE: an nlbl_type == NETLBL_NLTYPE_UNLABELED is a "fallback" label + * and is treated as if nlbl_sid == SECSID_NULL when a XFRM SID/label + * is present */ + if (nlbl_sid == SECSID_NULL || nlbl_type == NETLBL_NLTYPE_UNLABELED) { + *peer_sid = xfrm_sid; + return 0; + } + + /* we don't need to check ss_initialized here since the only way both + * nlbl_sid and xfrm_sid are not equal to SECSID_NULL would be if the + * security server was initialized and ss_initialized was true */ + if (!selinux_mls_enabled) { + *peer_sid = SECSID_NULL; + return 0; + } + + POLICY_RDLOCK; + + nlbl_ctx = sidtab_search(&sidtab, nlbl_sid); + if (!nlbl_ctx) { + printk(KERN_ERR + "security_sid_mls_cmp: unrecognized SID %d\n", + nlbl_sid); + rc = -EINVAL; + goto out_slowpath; + } + xfrm_ctx = sidtab_search(&sidtab, xfrm_sid); + if (!xfrm_ctx) { + printk(KERN_ERR + "security_sid_mls_cmp: unrecognized SID %d\n", + xfrm_sid); + rc = -EINVAL; + goto out_slowpath; + } + rc = (mls_context_cmp(nlbl_ctx, xfrm_ctx) ? 0 : -EACCES); + +out_slowpath: + POLICY_RDUNLOCK; + if (rc == 0) + /* at present NetLabel SIDs/labels really only carry MLS + * information so if the MLS portion of the NetLabel SID + * matches the MLS portion of the labeled XFRM SID/label + * then pass along the XFRM SID as it is the most + * expressive */ + *peer_sid = xfrm_sid; + else + *peer_sid = SECSID_NULL; + return rc; +} + static int get_classes_callback(void *k, void *d, void *args) { struct class_datum *datum = d; |