diff options
-rw-r--r-- | include/linux/in.h | 1 | ||||
-rw-r--r-- | include/linux/security.h | 25 | ||||
-rw-r--r-- | include/linux/socket.h | 1 | ||||
-rw-r--r-- | net/core/sock.c | 2 | ||||
-rw-r--r-- | net/ipv4/ip_sockglue.c | 31 | ||||
-rw-r--r-- | security/dummy.c | 10 | ||||
-rw-r--r-- | security/selinux/hooks.c | 46 | ||||
-rw-r--r-- | security/selinux/include/xfrm.h | 2 | ||||
-rw-r--r-- | security/selinux/xfrm.c | 68 |
9 files changed, 169 insertions, 17 deletions
diff --git a/include/linux/in.h b/include/linux/in.h index ba355384016..94f557fa463 100644 --- a/include/linux/in.h +++ b/include/linux/in.h @@ -72,6 +72,7 @@ struct in_addr { #define IP_FREEBIND 15 #define IP_IPSEC_POLICY 16 #define IP_XFRM_POLICY 17 +#define IP_PASSSEC 18 /* BSD compatibility */ #define IP_RECVRETOPTS IP_RETOPTS diff --git a/include/linux/security.h b/include/linux/security.h index 7cbef482e13..b18eb8cfa63 100644 --- a/include/linux/security.h +++ b/include/linux/security.h @@ -1286,7 +1286,8 @@ struct security_operations { int (*socket_setsockopt) (struct socket * sock, int level, int optname); int (*socket_shutdown) (struct socket * sock, int how); int (*socket_sock_rcv_skb) (struct sock * sk, struct sk_buff * skb); - int (*socket_getpeersec) (struct socket *sock, char __user *optval, int __user *optlen, unsigned len); + int (*socket_getpeersec_stream) (struct socket *sock, char __user *optval, int __user *optlen, unsigned len); + int (*socket_getpeersec_dgram) (struct sk_buff *skb, char **secdata, u32 *seclen); int (*sk_alloc_security) (struct sock *sk, int family, gfp_t priority); void (*sk_free_security) (struct sock *sk); unsigned int (*sk_getsid) (struct sock *sk, struct flowi *fl, u8 dir); @@ -2741,10 +2742,16 @@ static inline int security_sock_rcv_skb (struct sock * sk, return security_ops->socket_sock_rcv_skb (sk, skb); } -static inline int security_socket_getpeersec(struct socket *sock, char __user *optval, - int __user *optlen, unsigned len) +static inline int security_socket_getpeersec_stream(struct socket *sock, char __user *optval, + int __user *optlen, unsigned len) { - return security_ops->socket_getpeersec(sock, optval, optlen, len); + return security_ops->socket_getpeersec_stream(sock, optval, optlen, len); +} + +static inline int security_socket_getpeersec_dgram(struct sk_buff *skb, char **secdata, + u32 *seclen) +{ + return security_ops->socket_getpeersec_dgram(skb, secdata, seclen); } static inline int security_sk_alloc(struct sock *sk, int family, gfp_t priority) @@ -2863,8 +2870,14 @@ static inline int security_sock_rcv_skb (struct sock * sk, return 0; } -static inline int security_socket_getpeersec(struct socket *sock, char __user *optval, - int __user *optlen, unsigned len) +static inline int security_socket_getpeersec_stream(struct socket *sock, char __user *optval, + int __user *optlen, unsigned len) +{ + return -ENOPROTOOPT; +} + +static inline int security_socket_getpeersec_dgram(struct sk_buff *skb, char **secdata, + u32 *seclen) { return -ENOPROTOOPT; } diff --git a/include/linux/socket.h b/include/linux/socket.h index b02dda4ee83..9ab2ddd8022 100644 --- a/include/linux/socket.h +++ b/include/linux/socket.h @@ -150,6 +150,7 @@ __KINLINE struct cmsghdr * cmsg_nxthdr (struct msghdr *__msg, struct cmsghdr *__ #define SCM_RIGHTS 0x01 /* rw: access rights (array of int) */ #define SCM_CREDENTIALS 0x02 /* rw: struct ucred */ +#define SCM_SECURITY 0x03 /* rw: security label */ struct ucred { __u32 pid; diff --git a/net/core/sock.c b/net/core/sock.c index 6e00811d44b..5038a5a7bd8 100644 --- a/net/core/sock.c +++ b/net/core/sock.c @@ -616,7 +616,7 @@ int sock_getsockopt(struct socket *sock, int level, int optname, break; case SO_PEERSEC: - return security_socket_getpeersec(sock, optval, optlen, len); + return security_socket_getpeersec_stream(sock, optval, optlen, len); default: return(-ENOPROTOOPT); diff --git a/net/ipv4/ip_sockglue.c b/net/ipv4/ip_sockglue.c index 2bf8d782f67..b5c4f61518e 100644 --- a/net/ipv4/ip_sockglue.c +++ b/net/ipv4/ip_sockglue.c @@ -50,6 +50,7 @@ #define IP_CMSG_TOS 4 #define IP_CMSG_RECVOPTS 8 #define IP_CMSG_RETOPTS 16 +#define IP_CMSG_PASSSEC 32 /* * SOL_IP control messages. @@ -109,6 +110,19 @@ static void ip_cmsg_recv_retopts(struct msghdr *msg, struct sk_buff *skb) put_cmsg(msg, SOL_IP, IP_RETOPTS, opt->optlen, opt->__data); } +static void ip_cmsg_recv_security(struct msghdr *msg, struct sk_buff *skb) +{ + char *secdata; + u32 seclen; + int err; + + err = security_socket_getpeersec_dgram(skb, &secdata, &seclen); + if (err) + return; + + put_cmsg(msg, SOL_IP, SCM_SECURITY, seclen, secdata); +} + void ip_cmsg_recv(struct msghdr *msg, struct sk_buff *skb) { @@ -138,6 +152,11 @@ void ip_cmsg_recv(struct msghdr *msg, struct sk_buff *skb) if (flags & 1) ip_cmsg_recv_retopts(msg, skb); + if ((flags>>=1) == 0) + return; + + if (flags & 1) + ip_cmsg_recv_security(msg, skb); } int ip_cmsg_send(struct msghdr *msg, struct ipcm_cookie *ipc) @@ -393,7 +412,8 @@ int ip_setsockopt(struct sock *sk, int level, int optname, char __user *optval, (1<<IP_RETOPTS) | (1<<IP_TOS) | (1<<IP_TTL) | (1<<IP_HDRINCL) | (1<<IP_MTU_DISCOVER) | (1<<IP_RECVERR) | - (1<<IP_ROUTER_ALERT) | (1<<IP_FREEBIND))) || + (1<<IP_ROUTER_ALERT) | (1<<IP_FREEBIND) | + (1<<IP_PASSSEC))) || optname == IP_MULTICAST_TTL || optname == IP_MULTICAST_LOOP) { if (optlen >= sizeof(int)) { @@ -478,6 +498,12 @@ int ip_setsockopt(struct sock *sk, int level, int optname, char __user *optval, else inet->cmsg_flags &= ~IP_CMSG_RETOPTS; break; + case IP_PASSSEC: + if (val) + inet->cmsg_flags |= IP_CMSG_PASSSEC; + else + inet->cmsg_flags &= ~IP_CMSG_PASSSEC; + break; case IP_TOS: /* This sets both TOS and Precedence */ if (sk->sk_type == SOCK_STREAM) { val &= ~3; @@ -932,6 +958,9 @@ int ip_getsockopt(struct sock *sk, int level, int optname, char __user *optval, case IP_RETOPTS: val = (inet->cmsg_flags & IP_CMSG_RETOPTS) != 0; break; + case IP_PASSSEC: + val = (inet->cmsg_flags & IP_CMSG_PASSSEC) != 0; + break; case IP_TOS: val = inet->tos; break; diff --git a/security/dummy.c b/security/dummy.c index f1a5bd98bf1..a326d6e0b6d 100644 --- a/security/dummy.c +++ b/security/dummy.c @@ -763,8 +763,14 @@ static int dummy_socket_sock_rcv_skb (struct sock *sk, struct sk_buff *skb) return 0; } -static int dummy_socket_getpeersec(struct socket *sock, char __user *optval, - int __user *optlen, unsigned len) +static int dummy_socket_getpeersec_stream(struct socket *sock, char __user *optval, + int __user *optlen, unsigned len) +{ + return -ENOPROTOOPT; +} + +static int dummy_socket_getpeersec_dgram(struct sk_buff *skb, char **secdata, + u32 *seclen) { return -ENOPROTOOPT; } diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index b65c201e9ff..5b16196f282 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -3318,24 +3318,38 @@ out: return err; } -static int selinux_socket_getpeersec(struct socket *sock, char __user *optval, - int __user *optlen, unsigned len) +static int selinux_socket_getpeersec_stream(struct socket *sock, char __user *optval, + int __user *optlen, unsigned len) { int err = 0; char *scontext; u32 scontext_len; struct sk_security_struct *ssec; struct inode_security_struct *isec; + u32 peer_sid = 0; isec = SOCK_INODE(sock)->i_security; - if (isec->sclass != SECCLASS_UNIX_STREAM_SOCKET) { + + /* if UNIX_STREAM check peer_sid, if TCP check dst for labelled sa */ + if (isec->sclass == SECCLASS_UNIX_STREAM_SOCKET) { + ssec = sock->sk->sk_security; + peer_sid = ssec->peer_sid; + } + else if (isec->sclass == SECCLASS_TCP_SOCKET) { + peer_sid = selinux_socket_getpeer_stream(sock->sk); + + if (peer_sid == SECSID_NULL) { + err = -ENOPROTOOPT; + goto out; + } + } + else { err = -ENOPROTOOPT; goto out; } - ssec = sock->sk->sk_security; - - err = security_sid_to_context(ssec->peer_sid, &scontext, &scontext_len); + err = security_sid_to_context(peer_sid, &scontext, &scontext_len); + if (err) goto out; @@ -3356,6 +3370,23 @@ out: return err; } +static int selinux_socket_getpeersec_dgram(struct sk_buff *skb, char **secdata, u32 *seclen) +{ + int err = 0; + u32 peer_sid = selinux_socket_getpeer_dgram(skb); + + if (peer_sid == SECSID_NULL) + return -EINVAL; + + err = security_sid_to_context(peer_sid, secdata, seclen); + if (err) + return err; + + return 0; +} + + + static int selinux_sk_alloc_security(struct sock *sk, int family, gfp_t priority) { return sk_alloc_security(sk, family, priority); @@ -4344,7 +4375,8 @@ static struct security_operations selinux_ops = { .socket_setsockopt = selinux_socket_setsockopt, .socket_shutdown = selinux_socket_shutdown, .socket_sock_rcv_skb = selinux_socket_sock_rcv_skb, - .socket_getpeersec = selinux_socket_getpeersec, + .socket_getpeersec_stream = selinux_socket_getpeersec_stream, + .socket_getpeersec_dgram = selinux_socket_getpeersec_dgram, .sk_alloc_security = selinux_sk_alloc_security, .sk_free_security = selinux_sk_free_security, .sk_getsid = selinux_sk_getsid_security, diff --git a/security/selinux/include/xfrm.h b/security/selinux/include/xfrm.h index 8e87996c6dd..a7f388bff3f 100644 --- a/security/selinux/include/xfrm.h +++ b/security/selinux/include/xfrm.h @@ -39,6 +39,8 @@ static inline u32 selinux_no_sk_sid(struct flowi *fl) #ifdef CONFIG_SECURITY_NETWORK_XFRM int selinux_xfrm_sock_rcv_skb(u32 sid, struct sk_buff *skb); int selinux_xfrm_postroute_last(u32 isec_sid, struct sk_buff *skb); +u32 selinux_socket_getpeer_stream(struct sock *sk); +u32 selinux_socket_getpeer_dgram(struct sk_buff *skb); #else static inline int selinux_xfrm_sock_rcv_skb(u32 isec_sid, struct sk_buff *skb) { diff --git a/security/selinux/xfrm.c b/security/selinux/xfrm.c index b2af7ca496c..dfab6c88669 100644 --- a/security/selinux/xfrm.c +++ b/security/selinux/xfrm.c @@ -225,6 +225,74 @@ void selinux_xfrm_state_free(struct xfrm_state *x) } /* + * SELinux internal function to retrieve the context of a connected + * (sk->sk_state == TCP_ESTABLISHED) TCP socket based on its security + * association used to connect to the remote socket. + * + * Retrieve via getsockopt SO_PEERSEC. + */ +u32 selinux_socket_getpeer_stream(struct sock *sk) +{ + struct dst_entry *dst, *dst_test; + u32 peer_sid = SECSID_NULL; + + if (sk->sk_state != TCP_ESTABLISHED) + goto out; + + dst = sk_dst_get(sk); + if (!dst) + goto out; + + for (dst_test = dst; dst_test != 0; + dst_test = dst_test->child) { + struct xfrm_state *x = dst_test->xfrm; + + if (x && selinux_authorizable_xfrm(x)) { + struct xfrm_sec_ctx *ctx = x->security; + peer_sid = ctx->ctx_sid; + break; + } + } + dst_release(dst); + +out: + return peer_sid; +} + +/* + * SELinux internal function to retrieve the context of a UDP packet + * based on its security association used to connect to the remote socket. + * + * Retrieve via setsockopt IP_PASSSEC and recvmsg with control message + * type SCM_SECURITY. + */ +u32 selinux_socket_getpeer_dgram(struct sk_buff *skb) +{ + struct sec_path *sp; + + if (skb == NULL) + return SECSID_NULL; + + if (skb->sk->sk_protocol != IPPROTO_UDP) + return SECSID_NULL; + + sp = skb->sp; + if (sp) { + int i; + + for (i = sp->len-1; i >= 0; i--) { + struct xfrm_state *x = sp->x[i].xvec; + if (selinux_authorizable_xfrm(x)) { + struct xfrm_sec_ctx *ctx = x->security; + return ctx->ctx_sid; + } + } + } + + return SECSID_NULL; +} + +/* * LSM hook that controls access to unlabelled packets. If * a xfrm_state is authorizable (defined by macro) then it was * already authorized by the IPSec process. If not, then |