/* 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) {
        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) {
 
                                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;
                        }
 
 
 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,
 
 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)
 {
 {
        return 0;
 }
+
+static inline int selinux_netlbl_socket_setsockopt(struct socket *sock,
+                                                  int level,
+                                                  int optname)
+{
+       return 0;
+}
 #endif /* CONFIG_NETLABEL */
 
 #endif
 
 
        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 */