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 #include /* for local_port_range[] */ #include /* struct or_callable used in sock_rcv_skb */ +#include #include #include #include @@ -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; + 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; - 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; + sk_sid = sksec->sid; + sk_class = sksec->sclass; - if (!skb->dev) - goto out; - - err = sel_netif_sid(skb->iif, &if_sid); - if (err) - goto out; - - 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); +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; + + if (selinux_policycap_netpeer) { + err = selinux_skb_peerlbl_sid(skb, family, &peer_sid); if (err) - goto out; - - err = avc_has_perm(sock_sid, port_sid, - sock_class, recv_perm, ad); + 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 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 = avc_has_perm(sk_sid, skb->secmark, SECCLASS_PACKET, + PACKET__RECV, &ad); if (err) - goto out; + return err; - err = selinux_netlbl_sock_rcv_skb(sksec, skb, family, &ad); + err = selinux_skb_peerlbl_sid(skb, family, &peer_sid); if (err) - goto out; - - err = selinux_xfrm_sock_rcv_skb(sksec->sid, skb, &ad); -out: - return 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 | + * multiple, consistent labels | 0 | + * multiple, inconsistent labels | - | 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;