Re: [RFC-PATCH 3/3] selinux: Add support for the SCTP protocol

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Thanks for the update. I've added some comments inline.

I will update the patch as per your comments and send out a new version in about
3 or 4 weeks time.




----- Original Message -----
> From: Paul Moore <paul@xxxxxxxxxxxxxx>
> To: Richard Haines <richard_c_haines@xxxxxxxxxxxxxx>
> Cc: selinux@xxxxxxxxxxxxx; linux-sctp@xxxxxxxxxxxxxxx; 'Daniel Borkmann' <dborkman@xxxxxxxxxx>
> Sent: Monday, 8 December 2014, 3:58
> Subject: Re: [RFC-PATCH 3/3] selinux: Add support for the SCTP protocol
> 
> On Tuesday, December 02, 2014 02:16:07 PM Richard Haines wrote:
>>  Support SCTP protocol if enabled in the kernel.
>> 
>>  Signed-off-by: Richard Haines <richard_c_haines@xxxxxxxxxxxxxx>
>>  ---
>>   security/selinux/Makefile               |   2 +
>>   security/selinux/hooks.c                | 334 
> +++++++++++++++++++----------
>>   security/selinux/include/classmap.h     |   4 +
>>   security/selinux/include/sctp.h         |  66 +++++++
>>   security/selinux/include/sctp_private.h |  46 +++++
>>   security/selinux/netlabel.c             |  12 +-
>>   security/selinux/sctp.c                 | 318 +++++++++++++++++++++++++++
>>   7 files changed, 663 insertions(+), 119 deletions(-)
>>   create mode 100644 security/selinux/include/sctp.h
>>   create mode 100644 security/selinux/include/sctp_private.h
>>   create mode 100644 security/selinux/sctp.c
> 
> [NOTE: CC'ing Daniel Borkmann in case he hasn't seen this already.]
> 
> Hi Richard,
> 
> Thank you very much for sticking with this and taking the time to dig into 
> this a put together another patch set; I especially appreciate the comments in 
> the code, it has helped me understand the SCTP process a bit better.
> 
> Comments inline ...
> 
>>  diff --git a/security/selinux/Makefile b/security/selinux/Makefile
>>  index ad5cd76..a450c85 100644
>>  --- a/security/selinux/Makefile
>>  +++ b/security/selinux/Makefile
>>  @@ -13,6 +13,8 @@ selinux-$(CONFIG_SECURITY_NETWORK_XFRM) += xfrm.o
>> 
>>   selinux-$(CONFIG_NETLABEL) += netlabel.o
>> 
>>  +selinux-$(subst m,y,$(CONFIG_IP_SCTP)) += sctp.o
>>  +
>>   ccflags-y := -Isecurity/selinux -Isecurity/selinux/include
>> 
>>   $(addprefix $(obj)/,$(selinux-y)): $(obj)/flask.h
>>  diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
>>  index e03bad5..9e5ab56 100644
>>  --- a/security/selinux/hooks.c
>>  +++ b/security/selinux/hooks.c
>>  @@ -66,6 +66,7 @@
>>   #include <linux/tcp.h>
>>   #include <linux/udp.h>
>>   #include <linux/dccp.h>
>>  +#include <linux/sctp.h>
>>   #include <linux/quota.h>
>>   #include <linux/un.h>        /* for Unix socket types */
>>   #include <net/af_unix.h>    /* for Unix socket types */
>>  @@ -94,6 +95,7 @@
>>   #include "netlabel.h"
>>   #include "audit.h"
>>   #include "avc_ss.h"
>>  +#include "sctp.h"
>> 
>>   extern struct security_operations *security_ops;
>> 
>>  @@ -1185,8 +1187,11 @@ static inline u16 socket_type_to_security_class(int
>>  family, int type, int protoc case PF_INET6:
>>           switch (type) {
>>           case SOCK_STREAM:
>>  +        case SOCK_SEQPACKET:
>>               if (default_protocol_stream(protocol))
>>                   return SECCLASS_TCP_SOCKET;
>>  +            else if (protocol == IPPROTO_SCTP)
>>  +                return SECCLASS_SCTP_SOCKET;
>>               else
>>                   return SECCLASS_RAWIP_SOCKET;
>>           case SOCK_DGRAM:
>>  @@ -3726,6 +3731,24 @@ static int selinux_parse_skb_ipv4(struct sk_buff
>>  *skb, break;
>>       }
>> 
>>  +#if defined(CONFIG_IP_SCTP) || defined(CONFIG_IP_SCTP_MODULE)
>>  +    case IPPROTO_SCTP: {
>>  +        struct sctphdr _sctph, *sh;
>>  +
>>  +        if (ntohs(ih->frag_off) & IP_OFFSET)
>>  +            break;
>>  +
>>  +        offset += ihlen;
>>  +        sh = skb_header_pointer(skb, offset, sizeof(_sctph), &_sctph);
>>  +        if (sh == NULL)
>>  +            break;
>>  +
>>  +        ad->u.net->sport = sh->source;
>>  +        ad->u.net->dport = sh->dest;
>>  +        break;
>>  +    }
>>  +#endif
>>  +
>>       default:
>>           break;
>>       }
>>  @@ -3799,6 +3822,20 @@ static int selinux_parse_skb_ipv6(struct sk_buff
>>  *skb, break;
>>       }
>> 
>>  +#if defined(CONFIG_IP_SCTP) || defined(CONFIG_IP_SCTP_MODULE)
>>  +    case IPPROTO_SCTP: {
>>  +        struct sctphdr _sctph, *sh;
>>  +
>>  +        sh = skb_header_pointer(skb, offset, sizeof(_sctph), &_sctph);
>>  +        if (sh == NULL)
>>  +            break;
>>  +
>>  +        ad->u.net->sport = sh->source;
>>  +        ad->u.net->dport = sh->dest;
>>  +        break;
>>  +    }
>>  +#endif
>>  +
>>       /* includes fragments */
>>       default:
>>           break;
>>  @@ -3928,7 +3965,7 @@ static int socket_sockcreate_sid(const struct
>>  task_security_struct *tsec, socksid);
>>   }
>> 
>>  -static int sock_has_perm(struct task_struct *task, struct sock *sk, u32
>>    perms) 
>>  +int sock_has_perm(struct task_struct *task, struct sock *sk, u32
>>    perms) {
>>       struct sk_security_struct *sksec = sk->sk_security;
>>       struct common_audit_data ad;
>>  @@ -3998,6 +4035,93 @@ static int selinux_socket_post_create(struct socket
>>  *sock, int family, Need to determine whether we should perform a name_bind
>>      permission check between the socket and the port number. */
>> 
>>  +int selinux_sk_bind(struct sock *sk, struct sockaddr *address,
>>  +                    int addrlen, u16 family)
>>  +{
>>  +    int err = 0;
>>  +    char *addrp;
>>  +    struct sk_security_struct *sksec = sk->sk_security;
>>  +    struct common_audit_data ad;
>>  +    struct lsm_network_audit net = {0,};
>>  +    struct sockaddr_in *addr4 = NULL;
>>  +    struct sockaddr_in6 *addr6 = NULL;
>>  +    unsigned short snum;
>>  +    u32 sid, node_perm;
>>  +
>>  +    if (family == PF_INET) {
>>  +        addr4 = (struct sockaddr_in *)address;
>>  +        snum = ntohs(addr4->sin_port);
>>  +        addrp = (char *)&addr4->sin_addr.s_addr;
>>  +    } else {
>>  +        addr6 = (struct sockaddr_in6 *)address;
>>  +        snum = ntohs(addr6->sin6_port);
>>  +        addrp = (char *)&addr6->sin6_addr.s6_addr;
>>  +    }
>>  +
>>  +    if (snum) {
>>  +        int low, high;
>>  +
>>  +        inet_get_local_port_range(sock_net(sk), &low, &high);
>>  +
>>  +        if (snum < max(PROT_SOCK, low) || snum > high) {
>>  +            err = sel_netport_sid(sk->sk_protocol,
>>  +                          snum, &sid);
>>  +            if (err)
>>  +                goto out;
>>  +            ad.type = LSM_AUDIT_DATA_NET;
>>  +            ad.u.net = &net;
>>  +            ad.u.net->sport = htons(snum);
>>  +            ad.u.net->family = family;
>>  +            err = avc_has_perm(sksec->sid, sid,
>>  +                       sksec->sclass,
>>  +                       SOCKET__NAME_BIND, &ad);
>>  +            if (err)
>>  +                goto out;
>>  +        }
>>  +    }
>>  +
>>  +    switch (sksec->sclass) {
>>  +    case SECCLASS_TCP_SOCKET:
>>  +        node_perm = TCP_SOCKET__NODE_BIND;
>>  +        break;
>>  +
>>  +    case SECCLASS_UDP_SOCKET:
>>  +        node_perm = UDP_SOCKET__NODE_BIND;
>>  +        break;
>>  +
>>  +    case SECCLASS_DCCP_SOCKET:
>>  +        node_perm = DCCP_SOCKET__NODE_BIND;
>>  +        break;
>>  +
>>  +    case SECCLASS_SCTP_SOCKET:
>>  +        node_perm = SCTP_SOCKET__NODE_BIND;
>>  +        break;
>>  +
>>  +    default:
>>  +        node_perm = RAWIP_SOCKET__NODE_BIND;
>>  +        break;
>>  +    }
>>  +
>>  +    err = sel_netnode_sid(addrp, family, &sid);
>>  +    if (err)
>>  +        goto out;
>>  +
>>  +    ad.type = LSM_AUDIT_DATA_NET;
>>  +    ad.u.net = &net;
>>  +    ad.u.net->sport = htons(snum);
>>  +    ad.u.net->family = family;
>>  +
>>  +    if (family == PF_INET)
>>  +        ad.u.net->v4info.saddr = addr4->sin_addr.s_addr;
>>  +    else
>>  +        ad.u.net->v6info.saddr = addr6->sin6_addr;
>>  +
>>  +    err = avc_has_perm(sksec->sid, sid,
>>  +               sksec->sclass, node_perm, &ad);
>>  +out:
>>  +    return err;
>>  +}
>>  +
>>   static int selinux_socket_bind(struct socket *sock, struct sockaddr
>>  *address, int addrlen) {
>>       struct sock *sk = sock->sk;
>>  @@ -4006,93 +4130,61 @@ static int selinux_socket_bind(struct socket *sock,
>>  struct sockaddr *address, in
>> 
>>       err = sock_has_perm(current, sk, SOCKET__BIND);
>>       if (err)
>>  -        goto out;
>>  +        return err;
>> 
>>       /*
>>        * If PF_INET or PF_INET6, check name_bind permission for the port.
>>  -     * Multiple address binding for SCTP is not supported yet: we just
>>  -     * check the first address now.
>>  +     * Multiple address binding for SCTP is supported via the
>>  +     * selinux_sctp_validate_*() functions.
>>        */
>>       family = sk->sk_family;
>>  -    if (family == PF_INET || family == PF_INET6) {
>>  -        char *addrp;
>>  -        struct sk_security_struct *sksec = sk->sk_security;
>>  -        struct common_audit_data ad;
>>  -        struct lsm_network_audit net = {0,};
>>  -        struct sockaddr_in *addr4 = NULL;
>>  -        struct sockaddr_in6 *addr6 = NULL;
>>  -        unsigned short snum;
>>  -        u32 sid, node_perm;
>>  -
>>  -        if (family == PF_INET) {
>>  -            addr4 = (struct sockaddr_in *)address;
>>  -            snum = ntohs(addr4->sin_port);
>>  -            addrp = (char *)&addr4->sin_addr.s_addr;
>>  -        } else {
>>  -            addr6 = (struct sockaddr_in6 *)address;
>>  -            snum = ntohs(addr6->sin6_port);
>>  -            addrp = (char *)&addr6->sin6_addr.s6_addr;
>>  -        }
>>  -
>>  -        if (snum) {
>>  -            int low, high;
>>  -
>>  -            inet_get_local_port_range(sock_net(sk), &low, &high);
>>  -
>>  -            if (snum < max(PROT_SOCK, low) || snum > high) {
>>  -                err = sel_netport_sid(sk->sk_protocol,
>>  -                              snum, &sid);
>>  -                if (err)
>>  -                    goto out;
>>  -                ad.type = LSM_AUDIT_DATA_NET;
>>  -                ad.u.net = &net;
>>  -                ad.u.net->sport = htons(snum);
>>  -                ad.u.net->family = family;
>>  -                err = avc_has_perm(sksec->sid, sid,
>>  -                           sksec->sclass,
>>  -                           SOCKET__NAME_BIND, &ad);
>>  -                if (err)
>>  -                    goto out;
>>  -            }
>>  -        }
>>  -
>>  -        switch (sksec->sclass) {
>>  -        case SECCLASS_TCP_SOCKET:
>>  -            node_perm = TCP_SOCKET__NODE_BIND;
>>  -            break;
>>  -
>>  -        case SECCLASS_UDP_SOCKET:
>>  -            node_perm = UDP_SOCKET__NODE_BIND;
>>  -            break;
>>  +    if (family == PF_INET || family == PF_INET6)
>>  +        err = selinux_sk_bind(sk, address, addrlen, family);
>> 
>>  -        case SECCLASS_DCCP_SOCKET:
>>  -            node_perm = DCCP_SOCKET__NODE_BIND;
>>  -            break;
>>  +    return err;
>>  +}
>> 
>>  -        default:
>>  -            node_perm = RAWIP_SOCKET__NODE_BIND;
>>  -            break;
>>  -        }
> 
> Out of curiosity, why did you pull the bulk of selinux_socket_bind() out into 
> a separate function?
It seemed like a good idea at the time !!! I'll review.

> 
> I do agree that this function is a bit awkward (we could probably tweak things 
> a bit so that the bulk of the code doesn't lie inside an if-then block), but 
> 
> I'm not convinced another function is the answer.
> 
>>  +int selinux_sk_connect(struct sock *sk, struct sockaddr *address,
>>  +                            int addrlen)
>>  +{
>>  +    struct sk_security_struct *sksec = sk->sk_security;
>>  +    int err;
>>  +    struct common_audit_data ad;
>>  +    struct lsm_network_audit net = {0,};
>>  +    struct sockaddr_in *addr4 = NULL;
>>  +    struct sockaddr_in6 *addr6 = NULL;
>>  +    unsigned short snum;
>>  +    u32 sid, perm;
>>  +
>>  +    if (sk->sk_family == PF_INET) {
>>  +        addr4 = (struct sockaddr_in *)address;
>>  +        if (addrlen < sizeof(struct sockaddr_in))
>>  +            return -EINVAL;
>>  +        snum = ntohs(addr4->sin_port);
>>  +    } else {
>>  +        addr6 = (struct sockaddr_in6 *)address;
>>  +        if (addrlen < SIN6_LEN_RFC2133)
>>  +            return -EINVAL;
>>  +        snum = ntohs(addr6->sin6_port);
>>  +    }
>> 
>>  -        err = sel_netnode_sid(addrp, family, &sid);
>>  -        if (err)
>>  -            goto out;
>>  +    err = sel_netport_sid(sk->sk_protocol, snum, &sid);
>>  +    if (err)
>>  +        goto out;
>> 
>>  -        ad.type = LSM_AUDIT_DATA_NET;
>>  -        ad.u.net = &net;
>>  -        ad.u.net->sport = htons(snum);
>>  -        ad.u.net->family = family;
>>  +    if (sksec->sclass == SECCLASS_TCP_SOCKET)
>>  +        perm = TCP_SOCKET__NAME_CONNECT;
>>  +    else if (sksec->sclass == SECCLASS_DCCP_SOCKET)
>>  +        perm = DCCP_SOCKET__NAME_CONNECT;
>>  +    else if (sksec->sclass == SECCLASS_SCTP_SOCKET)
>>  +        perm = SCTP_SOCKET__NAME_CONNECT;
>> 
>>  -        if (family == PF_INET)
>>  -            ad.u.net->v4info.saddr = addr4->sin_addr.s_addr;
>>  -        else
>>  -            ad.u.net->v6info.saddr = addr6->sin6_addr;
>>  +    ad.type = LSM_AUDIT_DATA_NET;
>>  +    ad.u.net = &net;
>>  +    ad.u.net->dport = htons(snum);
>>  +    ad.u.net->family = sk->sk_family;
>>  +    err = avc_has_perm(sksec->sid, sid, sksec->sclass, perm, 
> &ad);
>> 
>>  -        err = avc_has_perm(sksec->sid, sid,
>>  -                   sksec->sclass, node_perm, &ad);
>>  -        if (err)
>>  -            goto out;
>>  -    }
>>   out:
>>       return err;
>>   }
>>  @@ -4108,48 +4200,18 @@ static int selinux_socket_connect(struct socket
>>  *sock, struct sockaddr *address, return err;
>> 
>>       /*
>>  -     * If a TCP or DCCP socket, check name_connect permission for the 
> port.
>>  +     * If a TCP, DCCP or SCTP socket, check name_connect permission
>>  +     * for the port.
>>        */
>>       if (sksec->sclass == SECCLASS_TCP_SOCKET ||
>>  -        sksec->sclass == SECCLASS_DCCP_SOCKET) {
>>  -        struct common_audit_data ad;
>>  -        struct lsm_network_audit net = {0,};
>>  -        struct sockaddr_in *addr4 = NULL;
>>  -        struct sockaddr_in6 *addr6 = NULL;
>>  -        unsigned short snum;
>>  -        u32 sid, perm;
>>  -
>>  -        if (sk->sk_family == PF_INET) {
>>  -            addr4 = (struct sockaddr_in *)address;
>>  -            if (addrlen < sizeof(struct sockaddr_in))
>>  -                return -EINVAL;
>>  -            snum = ntohs(addr4->sin_port);
>>  -        } else {
>>  -            addr6 = (struct sockaddr_in6 *)address;
>>  -            if (addrlen < SIN6_LEN_RFC2133)
>>  -                return -EINVAL;
>>  -            snum = ntohs(addr6->sin6_port);
>>  -        }
>>  -
>>  -        err = sel_netport_sid(sk->sk_protocol, snum, &sid);
>>  -        if (err)
>>  -            goto out;
>>  -
>>  -        perm = (sksec->sclass == SECCLASS_TCP_SOCKET) ?
>>  -               TCP_SOCKET__NAME_CONNECT : DCCP_SOCKET__NAME_CONNECT;
>>  -
>>  -        ad.type = LSM_AUDIT_DATA_NET;
>>  -        ad.u.net = &net;
>>  -        ad.u.net->dport = htons(snum);
>>  -        ad.u.net->family = sk->sk_family;
>>  -        err = avc_has_perm(sksec->sid, sid, sksec->sclass, perm, 
> &ad);
>>  +        sksec->sclass == SECCLASS_DCCP_SOCKET ||
>>  +        sksec->sclass == SECCLASS_SCTP_SOCKET) {
>>  +        err = selinux_sk_connect(sk, address, addrlen);
>>           if (err)
>>  -            goto out;
>>  +            return err;
>>       }
>> 
>>       err = selinux_netlbl_socket_connect(sk, address);
>>  -
>>  -out:
>>       return err;
>>   }
> 
> The same thing here, why the new function?
> 
>>  @@ -4200,7 +4262,9 @@ static int selinux_socket_getpeername(struct socket
>>  *sock) return sock_has_perm(current, sock->sk, SOCKET__GETATTR);
>>   }
>> 
>>  -static int selinux_socket_setsockopt(struct socket *sock, int level, int
>>  optname) 
>>  +static int selinux_socket_setsockopt(struct socket *sock, int
>>    level,
>>  +                    int optname, char __user *optval,
>>  +                    int optlen)
>>   {
>>       int err;
>> 
>>  @@ -4208,13 +4272,26 @@ static int selinux_socket_setsockopt(struct socket
>>  *sock, int level, int optname if (err)
>>           return err;
>> 
>>  +    err = selinux_sctp_validate_setsockopt(sock->sk, level, optname,
>>  +                               optval, optlen);
>>  +    if (err)
>>  +        return err;
>>  +
>>       return selinux_netlbl_socket_setsockopt(sock, level, optname);
>>   }
>> 
>>   static int selinux_socket_getsockopt(struct socket *sock, int level,
>>  -                     int optname)
>>  +                    int optname, char __user *optval,
>>  +                    int __user *optlen)
>>   {
>>  -    return sock_has_perm(current, sock->sk, SOCKET__GETOPT);
>>  +    int err;
>>  +
>>  +    err = sock_has_perm(current, sock->sk, SOCKET__GETOPT);
>>  +    if (err)
>>  +        return err;
>>  +
>>  +    return selinux_sctp_validate_getsockopt(sock->sk, level, optname,
>>  +                                optval, optlen);
>>   }
>> 
>>   static int selinux_socket_shutdown(struct socket *sock, int how)
>>  @@ -4385,6 +4462,16 @@ static int selinux_socket_sock_rcv_skb(struct sock
>>  *sk, struct sk_buff *skb) selinux_netlbl_err(skb, err, 0);
>>               return err;
>>           }
>>  +        /* The peer label has been validated. If SCTP class then
>>  +         * update sksec->peer_sid with the new peer_sid so it can be
>>  +         * retrieved by getpeercon(3).
>>  +         * Note: Because SCTP can have associations with multiple
>>  +         * peers, it is possible that each will have a different
>>  +         * peer label, therefore getpeercon(3) will only reflect the
>>  +         * last one processed here. */
>>  +        if (sksec->sclass == SECCLASS_SCTP_SOCKET &&
>>  +            sksec->peer_sid != peer_sid)
>>  +            sksec->peer_sid = peer_sid;
> 
> I could use a SCTP refresher here, but I don't think this is correct.
> 
> With connectionless protocols, e.g. UDP, there is not peer label since there 
> is no connected peer.  With connection based protocols, e.g. TCP, the peer 
> label is set during the early stages of the connection handshake, see 
> selinux_inet_conn_request(), and never changes during the lifetime of the 
> socket/connection.  I suspect SCTP, due to the association concept, is much 
> closer to TCP than UDP; we do want to set/track the peer label, but at the 
> early stages of the association setup, not here.
> 
> If there is an issue with multiple associations sharing a single socket then 
> we should look a bit more closely at how the associations are established.  
> Likely the first association on a socket would be the one that would set the 
> peer label, and then each additional association would need to have the exact 
> same security label, else the association would be denied.

Thanks for the clarification. I'll work through this again.

> 
>>       }
>> 
>>       if (secmark_active) {
>>  @@ -4407,7 +4494,12 @@ static int selinux_socket_getpeersec_stream(struct
>>  socket *sock, char __user *op u32 peer_sid = SECSID_NULL;
>> 
>>       if (sksec->sclass == SECCLASS_UNIX_STREAM_SOCKET ||
>>  -        sksec->sclass == SECCLASS_TCP_SOCKET)
>>  +        sksec->sclass == SECCLASS_TCP_SOCKET ||
>>  +        sksec->sclass == SECCLASS_SCTP_SOCKET)
>>  +        /* Note: Because SCTP can have associations with multiple
>>  +         * peers, it is possible that each will have a different
>>  +         * peer label, therefore this will only reflect the last
>>  +         * one processed by selinux_socket_sock_rcv_skb() */
>>           peer_sid = sksec->peer_sid;
> 
> See above comment.
> 
>>       if (peer_sid == SECSID_NULL)
>>           return -ENOPROTOOPT;
>>  @@ -4805,6 +4897,8 @@ static unsigned int selinux_ip_output(struct sk_buff
>>  *skb, if (sk) {
>>           struct sk_security_struct *sksec;
>> 
>>  +        /* Note that TCP_LISTEN equates to SCTP_SS_LISTENING
>>  +         * and DCCP_LISTEN socket states */
>>           if (sk->sk_state == TCP_LISTEN)
>>               /* if the socket is the listening state then this
>>                * packet is a SYN-ACK packet which means it needs to
>>  @@ -4910,7 +5004,9 @@ static unsigned int selinux_ip_postroute(struct
>>  sk_buff *skb, int ifindex,
>>     *       TCP listening state we cannot wait until the XFRM processing
>>     *       is done as we will miss out on the SA label if we do;
>>        *       unfortunately, this means more work, but it is only once per
>>  -     *       connection. */
>>  +     *       connection.
>>  +     * NOTE: that TCP_LISTEN equates to SCTP_SS_LISTENING and DCCP_LISTEN
>>  +     * socket states */
> 
> This is another thing we need to worry about.  Do you understand why there are 
> special cases here for TCP?  
Not really - I added lots of debugs around here to see what happened and could not
see anything that crippled anything, however as I was not sure what I was looking for !!!
This applied to the other areas I put comments regarding SCTP_SS_LISTENING and
DCCP_LISTEN states. I guess they may need narrowing down by adding
IPPROTO_TCP or IPROTO_SCTP (may be).
I'll poke around a bit more here and see how it goes.

> We may need to do something similar with SCTP 
> depending on the associations; I say may because I'm not sure if we'll 
> need to 
> do the special accept()/child socket labeling trick (TCP child socket's take 
> 
> the TE information from the parent server socket, but the MLS/MCS information 
> from the connection).

> 
>>       if (skb_dst(skb) != NULL && skb_dst(skb)->xfrm != NULL 
> &&
>>           !(sk != NULL && sk->sk_state == TCP_LISTEN))
>>           return NF_ACCEPT;
>>  @@ -4929,6 +5025,8 @@ static unsigned int selinux_ip_postroute(struct
>>  sk_buff *skb, int ifindex, secmark_perm = PACKET__SEND;
>>               peer_sid = SECINITSID_KERNEL;
>>           }
>>  +    /* Note that TCP_LISTEN equates to SCTP_SS_LISTENING and DCCP_LISTEN
>>  +     * socket states */
>>       } else if (sk->sk_state == TCP_LISTEN) {
>>           /* Locally generated packet but the associated socket is in the
>>            * listening state which means this is a SYN-ACK packet.  In
>>  diff --git a/security/selinux/include/classmap.h
>>  b/security/selinux/include/classmap.h index be491a7..8ab388a 100644
>>  --- a/security/selinux/include/classmap.h
>>  +++ b/security/selinux/include/classmap.h
>>  @@ -151,5 +151,9 @@ struct security_class_mapping secclass_map[] = {
>>       { "kernel_service", { "use_as_override", 
> "create_files_as", NULL } },
>>       { "tun_socket",
>>         { COMMON_SOCK_PERMS, "attach_queue", NULL } },
>>  +    { "sctp_socket",
>>  +      { COMMON_SOCK_PERMS, "node_bind", 
> "name_connect", "bindx_add",
>>  +        "bindx_rem", "connectx", "peeloff", 
> "set_addr", "set_params",
>>  +        NULL } },
>>       { NULL }
>>     };
>>  diff --git a/security/selinux/include/sctp.h
>>  b/security/selinux/include/sctp.h new file mode 100644
>>  index 0000000..e5c5de2
>>  --- /dev/null
>>  +++ b/security/selinux/include/sctp.h
>>  @@ -0,0 +1,66 @@
>>  +/*
>>  + * SELinux SCTP Support
>>  + *
>>  + * Provides SCTP function refs for getsockopt/setsockopt in
>>  selinux/hooks.c. + *
>>  + * Author: Richard Haines <richard_c_haines@xxxxxxxxxxxxxx>
>>  + *
>>  + */
>>  +
>>  +/*
>>  + * This program is free software;  you can redistribute it and/or modify
>>  + * it under the terms of the GNU General Public License as published by
>>  + * the Free Software Foundation; either version 2 of the License, or
>>  + * (at your option) any later version.
>>  + *
>>  + * This program is distributed in the hope that it will be useful,
>>  + * but WITHOUT ANY WARRANTY;  without even the implied warranty of
>>  + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See
>>  + * the GNU General Public License for more details.
>>  + *
>>  + * You should have received a copy of the GNU General Public License
>>  + * along with this program;  if not, write to the Free Software
>>  + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
>>  + *
>>  + */
>>  +
>>  +#ifndef _SELINUX_SCTP_H_
>>  +#define _SELINUX_SCTP_H_
>>  +
>>  +#include <linux/types.h>
>>  +#include <linux/net.h>
>>  +#include <net/sock.h>
>>  +
>>  +#if defined(CONFIG_IP_SCTP) || defined(CONFIG_IP_SCTP_MODULE)
>>  +int selinux_sctp_validate_setsockopt(struct sock *sk,
>>  +                    int level,
>>  +                    int optname,
>>  +                    char __user *optval,
>>  +                    int optlen);
>>  +
>>  +int selinux_sctp_validate_getsockopt(struct sock *sk,
>>  +                    int level,
>>  +                    int optname,
>>  +                    char __user *optval,
>>  +                    int __user *optlen);
>>  +#else
>>  +static inline int selinux_sctp_validate_setsockopt(struct sock *sk,
>>  +                    int level,
>>  +                    int optname,
>>  +                    char __user *optval,
>>  +                    int optlen)
>>  +{
>>  +    return 0;
>>  +}
>>  +
>>  +static inline int selinux_sctp_validate_getsockopt(struct sock *sk,
>>  +                    int level,
>>  +                    int optname,
>>  +                    char __user *optval,
>>  +                    int __user *optlen)
>>  +{
>>  +    return 0;
>>  +}
>>  +#endif  /* CONFIG_IP_SCTP */
>>  +
>>  +#endif
>>  diff --git a/security/selinux/include/sctp_private.h
>>  b/security/selinux/include/sctp_private.h new file mode 100644
>>  index 0000000..25dbaa3
>>  --- /dev/null
>>  +++ b/security/selinux/include/sctp_private.h
>>  @@ -0,0 +1,46 @@
>>  +/*
>>  + * SELinux SCTP Support
>>  + *
>>  + * Provides headers + extern selinux/hooks.c functions for selinux/sctp.c.
>>  + *
>>  + * Author: Richard Haines <richard_c_haines@xxxxxxxxxxxxxx>
>>  + *
>>  + */
>>  +
>>  +/*
>>  + * This program is free software;  you can redistribute it and/or modify
>>  + * it under the terms of the GNU General Public License as published by
>>  + * the Free Software Foundation; either version 2 of the License, or
>>  + * (at your option) any later version.
>>  + *
>>  + * This program is distributed in the hope that it will be useful,
>>  + * but WITHOUT ANY WARRANTY;  without even the implied warranty of
>>  + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See
>>  + * the GNU General Public License for more details.
>>  + *
>>  + * You should have received a copy of the GNU General Public License
>>  + * along with this program;  if not, write to the Free Software
>>  + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
>>  + *
>>  + */
>>  +
>>  +#include <linux/kernel.h>
>>  +#include <linux/init.h>
>>  +#include <linux/sched.h>
>>  +#include <linux/types.h>
>>  +#include <linux/net.h>
>>  +#include <net/sock.h>
>>  +#include <linux/sctp.h>
>>  +#include <uapi/linux/sctp.h>    /* For bindx setsocket option checks 
> */
>>  +
>>  +#include "security.h"
>>  +#include "avc.h"
>>  +
>>  +extern int sock_has_perm(struct task_struct *task, struct sock *sk, u32
>>    perms);
>>  +
>>  +extern int selinux_sk_bind(struct sock *sk, struct sockaddr *address,
>>  +                    int addrlen, u16 family);
>>  +
>>  +extern int selinux_sk_connect(struct sock *sk, struct sockaddr *address,
>>  +                            int addrlen);
>>  +
>>  diff --git a/security/selinux/netlabel.c b/security/selinux/netlabel.c
>>  index 0364120..0b04c8a 100644
>>  --- a/security/selinux/netlabel.c
>>  +++ b/security/selinux/netlabel.c
>>  @@ -396,13 +396,23 @@ int selinux_netlbl_sock_rcv_skb(struct
>>  sk_security_struct *sksec, case SECCLASS_TCP_SOCKET:
>>           perm = TCP_SOCKET__RECVFROM;
>>           break;
>>  +    case SECCLASS_SCTP_SOCKET:
>>  +        perm = SCTP_SOCKET__RECVFROM;
>>  +        break;
>>       default:
>>           perm = RAWIP_SOCKET__RECVFROM;
>>       }
>> 
>>       rc = avc_has_perm(sksec->sid, nlbl_sid, sksec->sclass, perm, 
> ad);
>>  -    if (rc == 0)
>>  +    if (rc == 0) {
>>  +        /* The peer label has been validated in compat mode.
>>  +         * If SCTP class then update sksec->peer_sid with the
>>  +         * nlbl_sid so it can be retrieved by getpeercon(3). */
>>  +        if (sksec->sclass == SECCLASS_SCTP_SOCKET &&
>>  +            sksec->peer_sid != nlbl_sid)
>>  +            sksec->peer_sid = nlbl_sid;
>>           return 0;
>>  +    }
> 
> See the other comments, this is likely not right.
> 
>>       if (nlbl_sid != SECINITSID_UNLABELED)
>>           netlbl_skbuff_err(skb, rc, 0);
>>  diff --git a/security/selinux/sctp.c b/security/selinux/sctp.c
>>  new file mode 100644
>>  index 0000000..1bd98a2
>>  --- /dev/null
>>  +++ b/security/selinux/sctp.c
>>  @@ -0,0 +1,318 @@
>>  +/*
>>  + * SELinux SCTP Support
>>  + *
>>  + * Provides security checks for the SCTP protocol.
>>  + *
>>  + * Author: Richard Haines <richard_c_haines@xxxxxxxxxxxxxx>
>>  + *
>>  + */
>>  +
>>  +/*
>>  + * This program is free software;  you can redistribute it and/or modify
>>  + * it under the terms of the GNU General Public License as published by
>>  + * the Free Software Foundation; either version 2 of the License, or
>>  + * (at your option) any later version.
>>  + *
>>  + * This program is distributed in the hope that it will be useful,
>>  + * but WITHOUT ANY WARRANTY;  without even the implied warranty of
>>  + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See
>>  + * the GNU General Public License for more details.
>>  + *
>>  + * You should have received a copy of the GNU General Public License
>>  + * along with this program;  if not, write to the Free Software
>>  + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
>>  + *
>>  + */
>>  +
>>  +#include "sctp_private.h"    /* #include files + extern 
> functions. */
>>  +
>>  +/**
>>  + * validate_sctp_addrs - Validate bindx and connectx addresses.
>>  + * @sk: the socket
>>  + * @optname: contains the name of the option to validate
>>  + * @addrs: structure containing one or more SCTP addresses
>>  + * @len: contains the length of the structure
>>  + *
>>  + * Description:
>>  + * This function will copy down the SCTP bindx and connectx addresses then
>>  + * check the appropriate permissions on each one via selinux_sk_bind() or
>>  + * selinux_sk_connect(). The code is a modified version from
>>  + * net/sctp/socket.c.
>>  + * Returns zero on success, negative values on failure.
>>  +*/
> 
> Based on the comment above, I have to wonder if we are doing the check in the 
> right place, I don't think the LSM should be doing __copy_from_user() 
> calls.  
> Is there a good place in the core SCTP code that we could hook to avoid a lot 
> of this?
Yes there are places as I've tested already. I plan to add a new security hook
as follows and plugged into net/sctp/socket.c code:

int security_sk_setsockopt(struct sock *sk, int level, int optname,
                    char *optval,
                    unsigned int optlen)
{
    return security_ops->sk_setsockopt(sk, level, optname, optval, optlen);
}
EXPORT_SYMBOL(security_sk_setsockopt);

This could then be used by other services that would need to set sockopts. Does
this seem reasonable ???


> 
>>  +static int validate_sctp_addrs(struct sock *sk, int optname,
>>  +                    struct sockaddr *addrs,
>>  +                    int len)
>>  +{
>>  +    struct sockaddr *kaddrs;
>>  +    int addrlen, err;
>>  +    void *addr_buf;
>>  +    struct sockaddr *address;
>>  +    int walk_size = 0;
>>  +
>>  +    /* Copy down the address information to process. */
>>  +    if (unlikely(len <= 0))
>>  +        return -EINVAL;
>>  +
>>  +    /* Check the user passed a healthy pointer.  */
>>  +    if (unlikely(!access_ok(VERIFY_READ, addrs, (unsigned int)len)))
>>  +        return -EFAULT;
>>  +
>>  +    /* Alloc space for the info in kernel memory.  */
>>  +    kaddrs = kmalloc((unsigned int)len, GFP_KERNEL);
>>  +    if (unlikely(!kaddrs))
>>  +        return -ENOMEM;
>>  +
>>  +    if (__copy_from_user(kaddrs, addrs, (unsigned int)len)) {
>>  +        kfree(kaddrs);
>>  +        return -EFAULT;
>>  +    }
>>  +
>>  +    addr_buf = kaddrs;
>>  +    /* Process list - may contain IPv4 or IPv6 addr's */
>>  +    while (walk_size < (unsigned int)len) {
>>  +        address = addr_buf;
>>  +
>>  +        switch (address->sa_family) {
>>  +        case PF_INET:
>>  +            addrlen = sizeof(struct sockaddr_in);
>>  +            break;
>>  +        case PF_INET6:
>>  +            addrlen = sizeof(struct sockaddr_in6);
>>  +            break;
>>  +        default:
>>  +            kfree(kaddrs);
>>  +            return -EINVAL;
>>  +        }
>>  +
>>  +        err = -EINVAL;
>>  +        if (optname == SCTP_SOCKOPT_BINDX_ADD) {
>>  +            pr_info("SELinux: bindx addr: %pISpc\n", 
> address);
>>  +            err = selinux_sk_bind(sk, address,
>>  +                    addrlen,
>>  +                    address->sa_family);
>>  +        } else if (optname == SCTP_SOCKOPT_CONNECTX ||
>>  +               optname == SCTP_SOCKOPT_CONNECTX3 ||
>>  +               optname == SCTP_SOCKOPT_CONNECTX_OLD) {
>>  +            pr_info("SELinux: connectx addr: %pISpc\n", 
> address);
>>  +            err = selinux_sk_connect(sk, address,
>>  +                    addrlen);
>>  +        }
>>  +        if (err) {
>>  +            kfree(kaddrs);
>>  +            return err;
>>  +        }
>>  +        addr_buf += addrlen;
>>  +        walk_size += addrlen;
>>  +    }
>>  +    kfree(kaddrs);
>>  +    return 0;
>>  +}
>>  +
>>  +/**
>>  + * selinux_sctp_validate_setsockopt - Check setsockopt values.
>>  + * @sk: the socket
>>  + * @level: contains the protocol level to validate
>>  + * @optname: contains the name of the option to validate
>>  + * @optval: contains the value(s) to set
>>  + * @optlen: contains the length of the value(s) to be set
>>  + *
>>  + * Description:
>>  + * Check whether SCTP socket options are allowed or not. Returns zero on
>>  + * success, negative values on failure.
>>  + *
>>  + * Supports the following socket options:
>>  + *
>>  + *    SCTP_SOCKOPT_BINDX_ADD - Allows additional bind addresses to be
>>  + *    associated after (optionally) calling bind(3).
>>  + *    sctp_bindx(3) - adds or removes a set of bind addresses on a socket.
>>  + *
>>  + *    SCTP_SOCKOPT_CONNECTX/_OLD - Allows the allocation of multiple
>>  + *    addresses for reaching a peer (multi-homed).
>>  + *    sctp_connectx(3) - initiate a connection on an SCTP socket using
>>  + *    multiple destination addresses.
>>  + *    NOTE: SCTP_SOCKOPT_CONNECTX3 is handled by the
>>  + *    selinux_sctp_validate_getsockopt() function is it may return an
>>  + *    association id.
>>  + *
>>  + *  Together they form SCTP associations with the address(s) passed over
>>    the
>>  + *  link to inform peer of any changes. As these two options can
>>    support
>>  + *  multiple addresses, each address is checked via
>>    selinux_sk_bind() or
>>  + *  selinux_sk_connect() to determine whether they
>>    have the correct
>>  + *  permissions:
>>  + *     bindx_add: name_bind, node_bind + node SID + port SID via
>>  + *                (portcon sctp port ctx) policy statement.
>>  + *     connectx:  name_connect + port SID via (portcon sctp port ctx)
>>  + *
>>  + *    SCTP_SOCKOPT_BINDX_REM - As the addresses have already been
>>  + *    allowed, only the bindx_rem permission is checked.
>>  + *
>>  + *    SCTP_PEER_ADDR_PARAMS - Alter heartbeats and address max
>>  + *    retransmissions.
>>  + *    SCTP_PEER_ADDR_THLDS - Alter the thresholds.
>>  + *  These require the set_params permission.
>>  + *
>>  + *    SCTP_PRIMARY_ADDR - Set local primary address.
>>  + *    SCTP_SET_PEER_PRIMARY_ADDR - Request peer sets address as
>>  + *    association primary.
>>  + *  These require the set_addr permission.
>>  +*/
>>  +int selinux_sctp_validate_setsockopt(struct sock *sk, int level, int
>>    optname,
>>  +                    char __user *optval, int optlen)
>>  +{
>>  +    int err;
>>  +
>>  +    if (level == SOL_SCTP) {
>>  +        switch (optname) {
>>  +        /* Allows additional addresses to be associated or removed
>>  +         * with an endpoint after (optionally) calling bind(). */
>>  +        case SCTP_SOCKOPT_BINDX_ADD:
>>  +            err = sock_has_perm(current, sk,
>>  +                SCTP_SOCKET__BINDX_ADD);
>>  +            if (err)
>>  +                return err;
>>  +
>>  +            err = validate_sctp_addrs(sk, optname,
>>  +                    (struct sockaddr __user *)optval, optlen);
>>  +            if (err)
>>  +                return err;
>>  +            break;
>>  +
>>  +        case SCTP_SOCKOPT_BINDX_REM:
>>  +            /* The addresses have been checked as they were
>>  +             * added, so just see if allowed to be removed. */
>>  +            err = sock_has_perm(current, sk,
>>  +                SCTP_SOCKET__BINDX_REM);
>>  +            if (err)
>>  +                return err;
>>  +            break;
>>  +
>>  +        /* Allows the allocation of multiple addresses for reaching
>>  +         * a peer (multi-homed). They form an SCTP 
> "association".
>>  +         * Note: CONNECTX3 requests go via getsockoption as it can
>>  +         * return an association id if required. */
>>  +        case SCTP_SOCKOPT_CONNECTX:    /* CONNECTX requests. */
>>  +        case SCTP_SOCKOPT_CONNECTX_OLD:    /* CONNECTX old requests. */
>>  +            err = sock_has_perm(current, sk,
>>  +                SCTP_SOCKET__CONNECTX);
>>  +            if (err)
>>  +                return err;
>>  +
>>  +            err = validate_sctp_addrs(sk, optname,
>>  +                    (struct sockaddr __user *)optval, optlen);
>>  +            if (err)
>>  +                return err;
>>  +            break;
>>  +
>>  +        /* Alter heartbeats and address max retransmissions. */
>>  +        case SCTP_PEER_ADDR_PARAMS:
>>  +        /* Alter the thresholds. */
>>  +        case SCTP_PEER_ADDR_THLDS:
>>  +            err = sock_has_perm(current, sk,
>>  +                    SCTP_SOCKET__SET_PARAMS);
>>  +            if (err)
>>  +                return err;
>>  +            break;
>>  +
>>  +        /* Set local primary address. */
>>  +        case SCTP_PRIMARY_ADDR:
>>  +        /* Request peer sets address as association primary. */
>>  +        case SCTP_SET_PEER_PRIMARY_ADDR:
>>  +            err = sock_has_perm(current, sk,
>>  +                    SCTP_SOCKET__SET_ADDR);
>>  +            if (err)
>>  +                return err;
>>  +            break;
>>  +
>>  +        default:
>>  +            break;
>>  +        }
>>  +    }
>>  +    return 0;
>>  +}
>>  +
>>  +/**
>>  + * selinux_sctp_validate_getsockopt - Check getsockopt values.
>>  + * @sk: the socket
>>  + * @level: contains the protocol level to validate
>>  + * @optname: contains the name of the option to validate
>>  + * @optval: contains the value(s) to get
>>  + * @optlen: contains the length of the value(s) to get
>>  + *
>>  + * Description:
>>  + * Check whether SCTP socket options are allowed or not. Returns zero on
>>  + * success, negative values on failure.
>>  + *
>>  + * Supports the following socket options:
>>  + *
>>  + *    SCTP_SOCKOPT_PEELOFF - Branch off an association into a new socket
>>  + *    that will be a one-to-one style socket. As SELinux already handles
>>  + *    the creation of new sockets, only the peeloff permission is checked.
> 
> This is interesting.  How exactly is the new socket created by SCTP, is the 
> SELinux labeling setup correctly?  What about the peer label?

The sctp code (net/sctp/socket.c sctp_do_peeloff()) calls sock_create() that
SELinux picks up. I did not touch the peer label as that was handled by
my bit of code in selinux_socket_sock_rcv_skb() that you want changed.

> 
>>  + *    SCTP_SOCKOPT_CONNECTX3 - Allows the allocation of multiple
>>  + *    addresses for reaching a peer (multi-homed).
>>  + *    sctp_connectx(3) - initiate a connection on an SCTP socket using
>>  + *    multiple destination addresses. See comments in
>>  + *    selinux_sctp_validate_setsockopt() for more detail.
>>  + *    NOTES: 1) This option is a 'getsockopt' as it may also 
> return an
>>  + *              association id.
>>  + *           2) This option requires some manipulation as the address
>>  + *              location and size are contained in 'struct
>>    sctp_getaddrs_old'
>>  + *              These need to be extracted and passed
>>    to the common
>>  + *              validate_sctp_addrs() function.
>>  +*/
>>  +int selinux_sctp_validate_getsockopt(struct sock *sk,
>>  +                    int level,
>>  +                    int optname,
>>  +                    char __user *optval,
>>  +                    int __user *optlen)
>>  +{
>>  +    int err;
>>  +
>>  +    if (level == SOL_SCTP) {
>>  +        struct sctp_getaddrs_old param;
>>  +        int len;
>>  +
>>  +        switch (optname) {
>>  +        /* Branch off an association into a new socket. */
>>  +        case SCTP_SOCKOPT_PEELOFF:
>>  +            err = sock_has_perm(current, sk,
>>  +                SCTP_SOCKET__PEELOFF);
>>  +            if (err)
>>  +                return err;
>>  +            break;
>>  +
>>  +        case SCTP_SOCKOPT_CONNECTX3:
>>  +            err = sock_has_perm(current, sk,
>>  +                SCTP_SOCKET__CONNECTX);
>>  +            if (err)
>>  +                return err;
>>  +
>>  +            /* The optval contains struct sctp_getaddrs_old that
>>  +             * is then used to reference the addresses to be
>>  +             * validated. */
>>  +            /* TODO net/sctp/socket.c sctp_getsockopt_connectx3()
>>  +             * has #ifdef CONFIG_COMPAT with different code.
>>  +             * Need to determine if needed here as not currently
>>  +             * implemented. */
>>  +
>>  +            if (get_user(len, optlen))
>>  +                return -EFAULT;
>>  +            if (len < sizeof(param))
>>  +                return -EINVAL;
>>  +            if (copy_from_user(&param, optval, sizeof(param)))
>>  +                return -EFAULT;
> 
> See my earlier comment about copy_from_user() and SELinux/LSM hooks.
> 
>>  +            len = param.addr_num;
>>  +            err = validate_sctp_addrs(sk, optname,
>>  +                (struct sockaddr __user *)param.addrs, len);
>>  +            if (err)
>>  +                return err;
>>  +            break;
>>  +
>>  +        default:
>>  +            break;
>>  +        }
>>  +    }
>>  +    return 0;
>>  +}
> 
> -- 
> paul moore
> www.paul-moore.com
> 

_______________________________________________
Selinux mailing list
Selinux@xxxxxxxxxxxxx
To unsubscribe, send email to Selinux-leave@xxxxxxxxxxxxx.
To get help, send an email containing "help" to Selinux-request@xxxxxxxxxxxxx.





[Index of Archives]     [Selinux Refpolicy]     [Linux SGX]     [Fedora Users]     [Fedora Desktop]     [Yosemite Photos]     [Yosemite Camping]     [Yosemite Campsites]     [KDE Users]     [Gnome Users]

  Powered by Linux