[NetLabel]: protect the CIPSOv4 socket option from setsockopt()
authorPaul Moore <paul.moore@hp.com>
Mon, 30 Oct 2006 23:22:15 +0000 (15:22 -0800)
committerDavid S. Miller <davem@sunset.davemloft.net>
Mon, 30 Oct 2006 23:24:49 +0000 (15:24 -0800)
This patch makes two changes to protect applications from either removing or
tampering with the CIPSOv4 IP option on a socket.  The first is the requirement
that applications have the CAP_NET_RAW capability to set an IPOPT_CIPSO option
on a socket; this prevents untrusted applications from setting their own
CIPSOv4 security attributes on the packets they send.  The second change is to
SELinux and it prevents applications from setting any IPv4 options when there
is an IPOPT_CIPSO option already present on the socket; this prevents
applications from removing CIPSOv4 security attributes from the packets they
send.

Signed-off-by: Paul Moore <paul.moore@hp.com>
Signed-off-by: James Morris <jmorris@namei.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
net/ipv4/cipso_ipv4.c
net/ipv4/ip_options.c
security/selinux/hooks.c
security/selinux/include/selinux_netlabel.h
security/selinux/ss/services.c

index e2077a3aa8c097156c34e4e12fd1f4a36320af08..6460233407c786f4138671765786d98db3bab6ae 100644 (file)
@@ -1307,7 +1307,8 @@ int cipso_v4_socket_setattr(const struct socket *sock,
 
        /* We can't use ip_options_get() directly because it makes a call to
         * ip_options_get_alloc() which allocates memory with GFP_KERNEL and
-        * we can't block here. */
+        * we won't always have CAP_NET_RAW even though we _always_ want to
+        * set the IPOPT_CIPSO option. */
        opt_len = (buf_len + 3) & ~3;
        opt = kzalloc(sizeof(*opt) + opt_len, GFP_ATOMIC);
        if (opt == NULL) {
@@ -1317,11 +1318,9 @@ int cipso_v4_socket_setattr(const struct socket *sock,
        memcpy(opt->__data, buf, buf_len);
        opt->optlen = opt_len;
        opt->is_data = 1;
+       opt->cipso = sizeof(struct iphdr);
        kfree(buf);
        buf = NULL;
-       ret_val = ip_options_compile(opt, NULL);
-       if (ret_val != 0)
-               goto socket_setattr_failure;
 
        sk_inet = inet_sk(sk);
        if (sk_inet->is_icsk) {
index 8dabbfc312674bd574df7f69f2a5646146723c18..9f02917d6f45319de4680eade1dda3cf3440b050 100644 (file)
@@ -443,7 +443,7 @@ int ip_options_compile(struct ip_options * opt, struct sk_buff * skb)
                                opt->router_alert = optptr - iph;
                        break;
                      case IPOPT_CIPSO:
-                       if (opt->cipso) {
+                       if ((!skb && !capable(CAP_NET_RAW)) || opt->cipso) {
                                pp_ptr = optptr;
                                goto error;
                        }
index e9969a2fc8462116a5b8cb256e1e8cbc97a7fd3c..8ab5679a37a30324b9b61a61c159dd4978512447 100644 (file)
@@ -3313,7 +3313,13 @@ static int selinux_socket_getpeername(struct socket *sock)
 
 static int selinux_socket_setsockopt(struct socket *sock,int level,int optname)
 {
-       return socket_has_perm(current, sock, SOCKET__SETOPT);
+       int err;
+
+       err = socket_has_perm(current, sock, SOCKET__SETOPT);
+       if (err)
+               return err;
+
+       return selinux_netlbl_socket_setsockopt(sock, level, optname);
 }
 
 static int selinux_socket_getsockopt(struct socket *sock, int level,
index ecab4bddaaf4a4225d00e6990b53b8bdebe041b7..9de10cc2cef2321d9c2ff9cade4c5e04dc11dd9e 100644 (file)
@@ -53,6 +53,9 @@ void selinux_netlbl_sk_security_init(struct sk_security_struct *ssec,
 void selinux_netlbl_sk_clone_security(struct sk_security_struct *ssec,
                                      struct sk_security_struct *newssec);
 int selinux_netlbl_inode_permission(struct inode *inode, int mask);
+int selinux_netlbl_socket_setsockopt(struct socket *sock,
+                                    int level,
+                                    int optname);
 #else
 static inline void selinux_netlbl_cache_invalidate(void)
 {
@@ -114,6 +117,13 @@ static inline int selinux_netlbl_inode_permission(struct inode *inode,
 {
        return 0;
 }
+
+static inline int selinux_netlbl_socket_setsockopt(struct socket *sock,
+                                                  int level,
+                                                  int optname)
+{
+       return 0;
+}
 #endif /* CONFIG_NETLABEL */
 
 #endif
index b1f6fb36c6997cc40ea8cf350adf12a9218bf8bc..bfe122764c98c2638183a5d37390e63d5f62a73b 100644 (file)
@@ -2682,4 +2682,41 @@ u32 selinux_netlbl_socket_getpeersec_dgram(struct sk_buff *skb)
 
        return peer_sid;
 }
+
+/**
+ * selinux_netlbl_socket_setsockopt - Do not allow users to remove a NetLabel
+ * @sock: the socket
+ * @level: the socket level or protocol
+ * @optname: the socket option name
+ *
+ * Description:
+ * Check the setsockopt() call and if the user is trying to replace the IP
+ * options on a socket and a NetLabel is in place for the socket deny the
+ * access; otherwise allow the access.  Returns zero when the access is
+ * allowed, -EACCES when denied, and other negative values on error.
+ *
+ */
+int selinux_netlbl_socket_setsockopt(struct socket *sock,
+                                    int level,
+                                    int optname)
+{
+       int rc = 0;
+       struct inode *inode = SOCK_INODE(sock);
+       struct sk_security_struct *sksec = sock->sk->sk_security;
+       struct inode_security_struct *isec = inode->i_security;
+       struct netlbl_lsm_secattr secattr;
+
+       mutex_lock(&isec->lock);
+       if (level == IPPROTO_IP && optname == IP_OPTIONS &&
+           sksec->nlbl_state == NLBL_LABELED) {
+               netlbl_secattr_init(&secattr);
+               rc = netlbl_socket_getattr(sock, &secattr);
+               if (rc == 0 && (secattr.cache || secattr.mls_lvl_vld))
+                       rc = -EACCES;
+               netlbl_secattr_destroy(&secattr);
+       }
+       mutex_unlock(&isec->lock);
+
+       return rc;
+}
 #endif /* CONFIG_NETLABEL */