]> git.karo-electronics.de Git - karo-tx-linux.git/commitdiff
Merge branch 'stable-4.8' of git://git.infradead.org/users/pcmoore/selinux into next
authorJames Morris <james.l.morris@oracle.com>
Thu, 7 Jul 2016 00:15:34 +0000 (10:15 +1000)
committerJames Morris <james.l.morris@oracle.com>
Thu, 7 Jul 2016 00:15:34 +0000 (10:15 +1000)
36 files changed:
include/net/calipso.h [new file with mode: 0644]
include/net/inet_sock.h
include/net/ipv6.h
include/net/netlabel.h
include/uapi/linux/audit.h
include/uapi/linux/in6.h
net/dccp/ipv6.c
net/ipv4/cipso_ipv4.c
net/ipv4/tcp_input.c
net/ipv6/Makefile
net/ipv6/af_inet6.c
net/ipv6/calipso.c [new file with mode: 0644]
net/ipv6/exthdrs.c
net/ipv6/exthdrs_core.c
net/ipv6/ipv6_sockglue.c
net/ipv6/sysctl_net_ipv6.c
net/ipv6/tcp_ipv6.c
net/iucv/af_iucv.c
net/netlabel/Kconfig
net/netlabel/Makefile
net/netlabel/netlabel_calipso.c [new file with mode: 0644]
net/netlabel/netlabel_calipso.h [new file with mode: 0644]
net/netlabel/netlabel_domainhash.c
net/netlabel/netlabel_domainhash.h
net/netlabel/netlabel_kapi.c
net/netlabel/netlabel_mgmt.c
net/netlabel/netlabel_mgmt.h
net/netlabel/netlabel_unlabeled.c
net/netlabel/netlabel_user.c
security/selinux/hooks.c
security/selinux/include/netlabel.h
security/selinux/netlabel.c
security/selinux/selinuxfs.c
security/selinux/ss/ebitmap.c
security/selinux/ss/services.c
security/smack/smack_lsm.c

diff --git a/include/net/calipso.h b/include/net/calipso.h
new file mode 100644 (file)
index 0000000..b1b30cd
--- /dev/null
@@ -0,0 +1,91 @@
+/*
+ * CALIPSO - Common Architecture Label IPv6 Security Option
+ *
+ * This is an implementation of the CALIPSO protocol as specified in
+ * RFC 5570.
+ *
+ * Authors: Paul Moore <paul@paul-moore.com>
+ *          Huw Davies <huw@codeweavers.com>
+ *
+ */
+
+/*
+ * (c) Copyright Hewlett-Packard Development Company, L.P., 2006
+ * (c) Copyright Huw Davies <huw@codeweavers.com>, 2015
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef _CALIPSO_H
+#define _CALIPSO_H
+
+#include <linux/types.h>
+#include <linux/rcupdate.h>
+#include <linux/list.h>
+#include <linux/net.h>
+#include <linux/skbuff.h>
+#include <net/netlabel.h>
+#include <net/request_sock.h>
+#include <linux/atomic.h>
+#include <asm/unaligned.h>
+
+/* known doi values */
+#define CALIPSO_DOI_UNKNOWN          0x00000000
+
+/* doi mapping types */
+#define CALIPSO_MAP_UNKNOWN          0
+#define CALIPSO_MAP_PASS             2
+
+/*
+ * CALIPSO DOI definitions
+ */
+
+/* DOI definition struct */
+struct calipso_doi {
+       u32 doi;
+       u32 type;
+
+       atomic_t refcount;
+       struct list_head list;
+       struct rcu_head rcu;
+};
+
+/*
+ * Sysctl Variables
+ */
+extern int calipso_cache_enabled;
+extern int calipso_cache_bucketsize;
+
+#ifdef CONFIG_NETLABEL
+int __init calipso_init(void);
+void calipso_exit(void);
+bool calipso_validate(const struct sk_buff *skb, const unsigned char *option);
+#else
+static inline int __init calipso_init(void)
+{
+       return 0;
+}
+
+static inline void calipso_exit(void)
+{
+}
+static inline bool calipso_validate(const struct sk_buff *skb,
+                                   const unsigned char *option)
+{
+       return true;
+}
+#endif /* CONFIG_NETLABEL */
+
+#endif /* _CALIPSO_H */
index 012b1f91f3ec4e3b018fb1b900692342eaefe108..236a81034fefec203e27bb73b5a1e5d7d512f8d5 100644 (file)
@@ -97,7 +97,12 @@ struct inet_request_sock {
        u32                     ir_mark;
        union {
                struct ip_options_rcu   *opt;
-               struct sk_buff          *pktopts;
+#if IS_ENABLED(CONFIG_IPV6)
+               struct {
+                       struct ipv6_txoptions   *ipv6_opt;
+                       struct sk_buff          *pktopts;
+               };
+#endif
        };
 };
 
index 11a045281948f07e638deb4515e60eaaf8c70774..8fed1cd78658a6e088b63f7703290cb02c1969b6 100644 (file)
@@ -313,11 +313,19 @@ struct ipv6_txoptions *ipv6_renew_options(struct sock *sk,
                                          int newtype,
                                          struct ipv6_opt_hdr __user *newopt,
                                          int newoptlen);
+struct ipv6_txoptions *
+ipv6_renew_options_kern(struct sock *sk,
+                       struct ipv6_txoptions *opt,
+                       int newtype,
+                       struct ipv6_opt_hdr *newopt,
+                       int newoptlen);
 struct ipv6_txoptions *ipv6_fixup_options(struct ipv6_txoptions *opt_space,
                                          struct ipv6_txoptions *opt);
 
 bool ipv6_opt_accepted(const struct sock *sk, const struct sk_buff *skb,
                       const struct inet6_skb_parm *opt);
+struct ipv6_txoptions *ipv6_update_options(struct sock *sk,
+                                          struct ipv6_txoptions *opt);
 
 static inline bool ipv6_accept_ra(struct inet6_dev *idev)
 {
@@ -943,7 +951,7 @@ enum {
 int ipv6_find_hdr(const struct sk_buff *skb, unsigned int *offset, int target,
                  unsigned short *fragoff, int *fragflg);
 
-int ipv6_find_tlv(struct sk_buff *skb, int offset, int type);
+int ipv6_find_tlv(const struct sk_buff *skb, int offset, int type);
 
 struct in6_addr *fl6_update_dst(struct flowi6 *fl6,
                                const struct ipv6_txoptions *opt,
index 7b5a300de7f589a534ac315f24a1151674aa54a4..efe98068880f5559b97477374cd25f11957b150e 100644 (file)
@@ -40,6 +40,7 @@
 #include <linux/atomic.h>
 
 struct cipso_v4_doi;
+struct calipso_doi;
 
 /*
  * NetLabel - A management interface for maintaining network packet label
@@ -94,6 +95,8 @@ struct cipso_v4_doi;
 #define NETLBL_NLTYPE_UNLABELED_NAME    "NLBL_UNLBL"
 #define NETLBL_NLTYPE_ADDRSELECT        6
 #define NETLBL_NLTYPE_ADDRSELECT_NAME   "NLBL_ADRSEL"
+#define NETLBL_NLTYPE_CALIPSO           7
+#define NETLBL_NLTYPE_CALIPSO_NAME      "NLBL_CALIPSO"
 
 /*
  * NetLabel - Kernel API for accessing the network packet label mappings.
@@ -216,6 +219,63 @@ struct netlbl_lsm_secattr {
        } attr;
 };
 
+/**
+ * struct netlbl_calipso_ops - NetLabel CALIPSO operations
+ * @doi_add: add a CALIPSO DOI
+ * @doi_free: free a CALIPSO DOI
+ * @doi_getdef: returns a reference to a DOI
+ * @doi_putdef: releases a reference of a DOI
+ * @doi_walk: enumerate the DOI list
+ * @sock_getattr: retrieve the socket's attr
+ * @sock_setattr: set the socket's attr
+ * @sock_delattr: remove the socket's attr
+ * @req_setattr: set the req socket's attr
+ * @req_delattr: remove the req socket's attr
+ * @opt_getattr: retrieve attr from memory block
+ * @skbuff_optptr: find option in packet
+ * @skbuff_setattr: set the skbuff's attr
+ * @skbuff_delattr: remove the skbuff's attr
+ * @cache_invalidate: invalidate cache
+ * @cache_add: add cache entry
+ *
+ * Description:
+ * This structure is filled out by the CALIPSO engine and passed
+ * to the NetLabel core via a call to netlbl_calipso_ops_register().
+ * It enables the CALIPSO engine (and hence IPv6) to be compiled
+ * as a module.
+ */
+struct netlbl_calipso_ops {
+       int (*doi_add)(struct calipso_doi *doi_def,
+                      struct netlbl_audit *audit_info);
+       void (*doi_free)(struct calipso_doi *doi_def);
+       int (*doi_remove)(u32 doi, struct netlbl_audit *audit_info);
+       struct calipso_doi *(*doi_getdef)(u32 doi);
+       void (*doi_putdef)(struct calipso_doi *doi_def);
+       int (*doi_walk)(u32 *skip_cnt,
+                       int (*callback)(struct calipso_doi *doi_def, void *arg),
+                       void *cb_arg);
+       int (*sock_getattr)(struct sock *sk,
+                           struct netlbl_lsm_secattr *secattr);
+       int (*sock_setattr)(struct sock *sk,
+                           const struct calipso_doi *doi_def,
+                           const struct netlbl_lsm_secattr *secattr);
+       void (*sock_delattr)(struct sock *sk);
+       int (*req_setattr)(struct request_sock *req,
+                          const struct calipso_doi *doi_def,
+                          const struct netlbl_lsm_secattr *secattr);
+       void (*req_delattr)(struct request_sock *req);
+       int (*opt_getattr)(const unsigned char *calipso,
+                          struct netlbl_lsm_secattr *secattr);
+       unsigned char *(*skbuff_optptr)(const struct sk_buff *skb);
+       int (*skbuff_setattr)(struct sk_buff *skb,
+                             const struct calipso_doi *doi_def,
+                             const struct netlbl_lsm_secattr *secattr);
+       int (*skbuff_delattr)(struct sk_buff *skb);
+       void (*cache_invalidate)(void);
+       int (*cache_add)(const unsigned char *calipso_ptr,
+                        const struct netlbl_lsm_secattr *secattr);
+};
+
 /*
  * LSM security attribute operations (inline)
  */
@@ -385,6 +445,14 @@ int netlbl_cfg_cipsov4_map_add(u32 doi,
                               const struct in_addr *addr,
                               const struct in_addr *mask,
                               struct netlbl_audit *audit_info);
+int netlbl_cfg_calipso_add(struct calipso_doi *doi_def,
+                          struct netlbl_audit *audit_info);
+void netlbl_cfg_calipso_del(u32 doi, struct netlbl_audit *audit_info);
+int netlbl_cfg_calipso_map_add(u32 doi,
+                              const char *domain,
+                              const struct in6_addr *addr,
+                              const struct in6_addr *mask,
+                              struct netlbl_audit *audit_info);
 /*
  * LSM security attribute operations
  */
@@ -405,6 +473,12 @@ int netlbl_catmap_setlong(struct netlbl_lsm_catmap **catmap,
                          unsigned long bitmap,
                          gfp_t flags);
 
+/* Bitmap functions
+ */
+int netlbl_bitmap_walk(const unsigned char *bitmap, u32 bitmap_len,
+                      u32 offset, u8 state);
+void netlbl_bitmap_setbit(unsigned char *bitmap, u32 bit, u8 state);
+
 /*
  * LSM protocol operations (NetLabel LSM/kernel API)
  */
@@ -427,13 +501,13 @@ int netlbl_skbuff_setattr(struct sk_buff *skb,
 int netlbl_skbuff_getattr(const struct sk_buff *skb,
                          u16 family,
                          struct netlbl_lsm_secattr *secattr);
-void netlbl_skbuff_err(struct sk_buff *skb, int error, int gateway);
+void netlbl_skbuff_err(struct sk_buff *skb, u16 family, int error, int gateway);
 
 /*
  * LSM label mapping cache operations
  */
 void netlbl_cache_invalidate(void);
-int netlbl_cache_add(const struct sk_buff *skb,
+int netlbl_cache_add(const struct sk_buff *skb, u16 family,
                     const struct netlbl_lsm_secattr *secattr);
 
 /*
@@ -495,6 +569,24 @@ static inline int netlbl_cfg_cipsov4_map_add(u32 doi,
 {
        return -ENOSYS;
 }
+static inline int netlbl_cfg_calipso_add(struct calipso_doi *doi_def,
+                                        struct netlbl_audit *audit_info)
+{
+       return -ENOSYS;
+}
+static inline void netlbl_cfg_calipso_del(u32 doi,
+                                         struct netlbl_audit *audit_info)
+{
+       return;
+}
+static inline int netlbl_cfg_calipso_map_add(u32 doi,
+                                            const char *domain,
+                                            const struct in6_addr *addr,
+                                            const struct in6_addr *mask,
+                                            struct netlbl_audit *audit_info)
+{
+       return -ENOSYS;
+}
 static inline int netlbl_catmap_walk(struct netlbl_lsm_catmap *catmap,
                                     u32 offset)
 {
@@ -586,7 +678,7 @@ static inline void netlbl_cache_invalidate(void)
 {
        return;
 }
-static inline int netlbl_cache_add(const struct sk_buff *skb,
+static inline int netlbl_cache_add(const struct sk_buff *skb, u16 family,
                                   const struct netlbl_lsm_secattr *secattr)
 {
        return 0;
@@ -598,4 +690,7 @@ static inline struct audit_buffer *netlbl_audit_start(int type,
 }
 #endif /* CONFIG_NETLABEL */
 
+const struct netlbl_calipso_ops *
+netlbl_calipso_ops_register(const struct netlbl_calipso_ops *ops);
+
 #endif /* _NETLABEL_H */
index d820aa979620b5f75b071dffbd73429bfc360282..82e8aa59446b18cb1cc0dd5c0d79a2fe8454221e 100644 (file)
 #define AUDIT_MAC_IPSEC_EVENT  1415    /* Audit an IPSec event */
 #define AUDIT_MAC_UNLBL_STCADD 1416    /* NetLabel: add a static label */
 #define AUDIT_MAC_UNLBL_STCDEL 1417    /* NetLabel: del a static label */
+#define AUDIT_MAC_CALIPSO_ADD  1418    /* NetLabel: add CALIPSO DOI entry */
+#define AUDIT_MAC_CALIPSO_DEL  1419    /* NetLabel: del CALIPSO DOI entry */
 
 #define AUDIT_FIRST_KERN_ANOM_MSG   1700
 #define AUDIT_LAST_KERN_ANOM_MSG    1799
index 318a4828bf98c236fb156406c8c9ec189472b357..b39ea4f2e701d27edb0db76a94560557e3ae408c 100644 (file)
@@ -143,6 +143,7 @@ struct in6_flowlabel_req {
 #define IPV6_TLV_PAD1          0
 #define IPV6_TLV_PADN          1
 #define IPV6_TLV_ROUTERALERT   5
+#define IPV6_TLV_CALIPSO       7       /* RFC 5570 */
 #define IPV6_TLV_JUMBO         194
 #define IPV6_TLV_HAO           201     /* home address option */
 
index d176f4e66369a399f5fe8a440eb864dbac9c7542..01bacf5bc027430345a1ed450725b033476a0914 100644 (file)
@@ -216,14 +216,17 @@ static int dccp_v6_send_response(const struct sock *sk, struct request_sock *req
        skb = dccp_make_response(sk, dst, req);
        if (skb != NULL) {
                struct dccp_hdr *dh = dccp_hdr(skb);
+               struct ipv6_txoptions *opt;
 
                dh->dccph_checksum = dccp_v6_csum_finish(skb,
                                                         &ireq->ir_v6_loc_addr,
                                                         &ireq->ir_v6_rmt_addr);
                fl6.daddr = ireq->ir_v6_rmt_addr;
                rcu_read_lock();
-               err = ip6_xmit(sk, skb, &fl6, rcu_dereference(np->opt),
-                              np->tclass);
+               opt = ireq->ipv6_opt;
+               if (!opt)
+                       opt = rcu_dereference(np->opt);
+               err = ip6_xmit(sk, skb, &fl6, opt, np->tclass);
                rcu_read_unlock();
                err = net_xmit_eval(err);
        }
@@ -236,6 +239,7 @@ done:
 static void dccp_v6_reqsk_destructor(struct request_sock *req)
 {
        dccp_feat_list_purge(&dccp_rsk(req)->dreq_featneg);
+       kfree(inet_rsk(req)->ipv6_opt);
        kfree_skb(inet_rsk(req)->pktopts);
 }
 
@@ -494,7 +498,9 @@ static struct sock *dccp_v6_request_recv_sock(const struct sock *sk,
         * Yes, keeping reference count would be much more clever, but we make
         * one more one thing there: reattach optmem to newsk.
         */
-       opt = rcu_dereference(np->opt);
+       opt = ireq->ipv6_opt;
+       if (!opt)
+               opt = rcu_dereference(np->opt);
        if (opt) {
                opt = ipv6_dup_options(newsk, opt);
                RCU_INIT_POINTER(newnp->opt, opt);
index 40d6b87713a132666298cc8e8d875da6a6a1a387..72d6f056d863603c959e1d04b9f863909a37c758 100644 (file)
@@ -134,76 +134,6 @@ int cipso_v4_rbm_strictvalid = 1;
  * Helper Functions
  */
 
-/**
- * cipso_v4_bitmap_walk - Walk a bitmap looking for a bit
- * @bitmap: the bitmap
- * @bitmap_len: length in bits
- * @offset: starting offset
- * @state: if non-zero, look for a set (1) bit else look for a cleared (0) bit
- *
- * Description:
- * Starting at @offset, walk the bitmap from left to right until either the
- * desired bit is found or we reach the end.  Return the bit offset, -1 if
- * not found, or -2 if error.
- */
-static int cipso_v4_bitmap_walk(const unsigned char *bitmap,
-                               u32 bitmap_len,
-                               u32 offset,
-                               u8 state)
-{
-       u32 bit_spot;
-       u32 byte_offset;
-       unsigned char bitmask;
-       unsigned char byte;
-
-       /* gcc always rounds to zero when doing integer division */
-       byte_offset = offset / 8;
-       byte = bitmap[byte_offset];
-       bit_spot = offset;
-       bitmask = 0x80 >> (offset % 8);
-
-       while (bit_spot < bitmap_len) {
-               if ((state && (byte & bitmask) == bitmask) ||
-                   (state == 0 && (byte & bitmask) == 0))
-                       return bit_spot;
-
-               bit_spot++;
-               bitmask >>= 1;
-               if (bitmask == 0) {
-                       byte = bitmap[++byte_offset];
-                       bitmask = 0x80;
-               }
-       }
-
-       return -1;
-}
-
-/**
- * cipso_v4_bitmap_setbit - Sets a single bit in a bitmap
- * @bitmap: the bitmap
- * @bit: the bit
- * @state: if non-zero, set the bit (1) else clear the bit (0)
- *
- * Description:
- * Set a single bit in the bitmask.  Returns zero on success, negative values
- * on error.
- */
-static void cipso_v4_bitmap_setbit(unsigned char *bitmap,
-                                  u32 bit,
-                                  u8 state)
-{
-       u32 byte_spot;
-       u8 bitmask;
-
-       /* gcc always rounds to zero when doing integer division */
-       byte_spot = bit / 8;
-       bitmask = 0x80 >> (bit % 8);
-       if (state)
-               bitmap[byte_spot] |= bitmask;
-       else
-               bitmap[byte_spot] &= ~bitmask;
-}
-
 /**
  * cipso_v4_cache_entry_free - Frees a cache entry
  * @entry: the entry to free
@@ -840,10 +770,10 @@ static int cipso_v4_map_cat_rbm_valid(const struct cipso_v4_doi *doi_def,
                cipso_cat_size = doi_def->map.std->cat.cipso_size;
                cipso_array = doi_def->map.std->cat.cipso;
                for (;;) {
-                       cat = cipso_v4_bitmap_walk(bitmap,
-                                                  bitmap_len_bits,
-                                                  cat + 1,
-                                                  1);
+                       cat = netlbl_bitmap_walk(bitmap,
+                                                bitmap_len_bits,
+                                                cat + 1,
+                                                1);
                        if (cat < 0)
                                break;
                        if (cat >= cipso_cat_size ||
@@ -909,7 +839,7 @@ static int cipso_v4_map_cat_rbm_hton(const struct cipso_v4_doi *doi_def,
                }
                if (net_spot >= net_clen_bits)
                        return -ENOSPC;
-               cipso_v4_bitmap_setbit(net_cat, net_spot, 1);
+               netlbl_bitmap_setbit(net_cat, net_spot, 1);
 
                if (net_spot > net_spot_max)
                        net_spot_max = net_spot;
@@ -951,10 +881,10 @@ static int cipso_v4_map_cat_rbm_ntoh(const struct cipso_v4_doi *doi_def,
        }
 
        for (;;) {
-               net_spot = cipso_v4_bitmap_walk(net_cat,
-                                               net_clen_bits,
-                                               net_spot + 1,
-                                               1);
+               net_spot = netlbl_bitmap_walk(net_cat,
+                                             net_clen_bits,
+                                             net_spot + 1,
+                                             1);
                if (net_spot < 0) {
                        if (net_spot == -2)
                                return -EFAULT;
index d6c8f4cd080001a527f7c137021cc6a3f3604344..d663d5071d2630c5cd3279ed04b63a6d79101f98 100644 (file)
@@ -6114,6 +6114,9 @@ struct request_sock *inet_reqsk_alloc(const struct request_sock_ops *ops,
 
                kmemcheck_annotate_bitfield(ireq, flags);
                ireq->opt = NULL;
+#if IS_ENABLED(CONFIG_IPV6)
+               ireq->pktopts = NULL;
+#endif
                atomic64_set(&ireq->ir_cookie, 0);
                ireq->ireq_state = TCP_NEW_SYN_RECV;
                write_pnet(&ireq->ireq_net, sock_net(sk_listener));
index 6d8ea099213e5084927833570e326fcf2cef628a..c174ccb340a15bca6e712932ae5e7a9c34739efb 100644 (file)
@@ -22,6 +22,7 @@ ipv6-$(CONFIG_NETFILTER) += netfilter.o
 ipv6-$(CONFIG_IPV6_MULTIPLE_TABLES) += fib6_rules.o
 ipv6-$(CONFIG_PROC_FS) += proc.o
 ipv6-$(CONFIG_SYN_COOKIES) += syncookies.o
+ipv6-$(CONFIG_NETLABEL) += calipso.o
 
 ipv6-objs += $(ipv6-y)
 
index bfa86f040c161233d4dd9192d205ce0ba3976e4f..b0994a49dfca4c55a8385b2aec264f7119be8550 100644 (file)
@@ -60,6 +60,7 @@
 #ifdef CONFIG_IPV6_TUNNEL
 #include <net/ip6_tunnel.h>
 #endif
+#include <net/calipso.h>
 
 #include <asm/uaccess.h>
 #include <linux/mroute6.h>
@@ -977,6 +978,10 @@ static int __init inet6_init(void)
        if (err)
                goto pingv6_fail;
 
+       err = calipso_init();
+       if (err)
+               goto calipso_fail;
+
 #ifdef CONFIG_SYSCTL
        err = ipv6_sysctl_register();
        if (err)
@@ -987,8 +992,10 @@ out:
 
 #ifdef CONFIG_SYSCTL
 sysctl_fail:
-       pingv6_exit();
+       calipso_exit();
 #endif
+calipso_fail:
+       pingv6_exit();
 pingv6_fail:
        ipv6_packet_cleanup();
 ipv6_packet_fail:
diff --git a/net/ipv6/calipso.c b/net/ipv6/calipso.c
new file mode 100644 (file)
index 0000000..c53b92c
--- /dev/null
@@ -0,0 +1,1473 @@
+/*
+ * CALIPSO - Common Architecture Label IPv6 Security Option
+ *
+ * This is an implementation of the CALIPSO protocol as specified in
+ * RFC 5570.
+ *
+ * Authors: Paul Moore <paul.moore@hp.com>
+ *          Huw Davies <huw@codeweavers.com>
+ *
+ */
+
+/* (c) Copyright Hewlett-Packard Development Company, L.P., 2006, 2008
+ * (c) Copyright Huw Davies <huw@codeweavers.com>, 2015
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/rcupdate.h>
+#include <linux/list.h>
+#include <linux/spinlock.h>
+#include <linux/string.h>
+#include <linux/jhash.h>
+#include <linux/audit.h>
+#include <linux/slab.h>
+#include <net/ip.h>
+#include <net/icmp.h>
+#include <net/tcp.h>
+#include <net/netlabel.h>
+#include <net/calipso.h>
+#include <linux/atomic.h>
+#include <linux/bug.h>
+#include <asm/unaligned.h>
+#include <linux/crc-ccitt.h>
+
+/* Maximium size of the calipso option including
+ * the two-byte TLV header.
+ */
+#define CALIPSO_OPT_LEN_MAX (2 + 252)
+
+/* Size of the minimum calipso option including
+ * the two-byte TLV header.
+ */
+#define CALIPSO_HDR_LEN (2 + 8)
+
+/* Maximium size of the calipso option including
+ * the two-byte TLV header and upto 3 bytes of
+ * leading pad and 7 bytes of trailing pad.
+ */
+#define CALIPSO_OPT_LEN_MAX_WITH_PAD (3 + CALIPSO_OPT_LEN_MAX + 7)
+
+ /* Maximium size of u32 aligned buffer required to hold calipso
+  * option.  Max of 3 initial pad bytes starting from buffer + 3.
+  * i.e. the worst case is when the previous tlv finishes on 4n + 3.
+  */
+#define CALIPSO_MAX_BUFFER (6 + CALIPSO_OPT_LEN_MAX)
+
+/* List of available DOI definitions */
+static DEFINE_SPINLOCK(calipso_doi_list_lock);
+static LIST_HEAD(calipso_doi_list);
+
+/* Label mapping cache */
+int calipso_cache_enabled = 1;
+int calipso_cache_bucketsize = 10;
+#define CALIPSO_CACHE_BUCKETBITS     7
+#define CALIPSO_CACHE_BUCKETS        BIT(CALIPSO_CACHE_BUCKETBITS)
+#define CALIPSO_CACHE_REORDERLIMIT   10
+struct calipso_map_cache_bkt {
+       spinlock_t lock;
+       u32 size;
+       struct list_head list;
+};
+
+struct calipso_map_cache_entry {
+       u32 hash;
+       unsigned char *key;
+       size_t key_len;
+
+       struct netlbl_lsm_cache *lsm_data;
+
+       u32 activity;
+       struct list_head list;
+};
+
+static struct calipso_map_cache_bkt *calipso_cache;
+
+/* Label Mapping Cache Functions
+ */
+
+/**
+ * calipso_cache_entry_free - Frees a cache entry
+ * @entry: the entry to free
+ *
+ * Description:
+ * This function frees the memory associated with a cache entry including the
+ * LSM cache data if there are no longer any users, i.e. reference count == 0.
+ *
+ */
+static void calipso_cache_entry_free(struct calipso_map_cache_entry *entry)
+{
+       if (entry->lsm_data)
+               netlbl_secattr_cache_free(entry->lsm_data);
+       kfree(entry->key);
+       kfree(entry);
+}
+
+/**
+ * calipso_map_cache_hash - Hashing function for the CALIPSO cache
+ * @key: the hash key
+ * @key_len: the length of the key in bytes
+ *
+ * Description:
+ * The CALIPSO tag hashing function.  Returns a 32-bit hash value.
+ *
+ */
+static u32 calipso_map_cache_hash(const unsigned char *key, u32 key_len)
+{
+       return jhash(key, key_len, 0);
+}
+
+/**
+ * calipso_cache_init - Initialize the CALIPSO cache
+ *
+ * Description:
+ * Initializes the CALIPSO label mapping cache, this function should be called
+ * before any of the other functions defined in this file.  Returns zero on
+ * success, negative values on error.
+ *
+ */
+static int __init calipso_cache_init(void)
+{
+       u32 iter;
+
+       calipso_cache = kcalloc(CALIPSO_CACHE_BUCKETS,
+                               sizeof(struct calipso_map_cache_bkt),
+                               GFP_KERNEL);
+       if (!calipso_cache)
+               return -ENOMEM;
+
+       for (iter = 0; iter < CALIPSO_CACHE_BUCKETS; iter++) {
+               spin_lock_init(&calipso_cache[iter].lock);
+               calipso_cache[iter].size = 0;
+               INIT_LIST_HEAD(&calipso_cache[iter].list);
+       }
+
+       return 0;
+}
+
+/**
+ * calipso_cache_invalidate - Invalidates the current CALIPSO cache
+ *
+ * Description:
+ * Invalidates and frees any entries in the CALIPSO cache.  Returns zero on
+ * success and negative values on failure.
+ *
+ */
+static void calipso_cache_invalidate(void)
+{
+       struct calipso_map_cache_entry *entry, *tmp_entry;
+       u32 iter;
+
+       for (iter = 0; iter < CALIPSO_CACHE_BUCKETS; iter++) {
+               spin_lock_bh(&calipso_cache[iter].lock);
+               list_for_each_entry_safe(entry,
+                                        tmp_entry,
+                                        &calipso_cache[iter].list, list) {
+                       list_del(&entry->list);
+                       calipso_cache_entry_free(entry);
+               }
+               calipso_cache[iter].size = 0;
+               spin_unlock_bh(&calipso_cache[iter].lock);
+       }
+}
+
+/**
+ * calipso_cache_check - Check the CALIPSO cache for a label mapping
+ * @key: the buffer to check
+ * @key_len: buffer length in bytes
+ * @secattr: the security attribute struct to use
+ *
+ * Description:
+ * This function checks the cache to see if a label mapping already exists for
+ * the given key.  If there is a match then the cache is adjusted and the
+ * @secattr struct is populated with the correct LSM security attributes.  The
+ * cache is adjusted in the following manner if the entry is not already the
+ * first in the cache bucket:
+ *
+ *  1. The cache entry's activity counter is incremented
+ *  2. The previous (higher ranking) entry's activity counter is decremented
+ *  3. If the difference between the two activity counters is geater than
+ *     CALIPSO_CACHE_REORDERLIMIT the two entries are swapped
+ *
+ * Returns zero on success, -ENOENT for a cache miss, and other negative values
+ * on error.
+ *
+ */
+static int calipso_cache_check(const unsigned char *key,
+                              u32 key_len,
+                              struct netlbl_lsm_secattr *secattr)
+{
+       u32 bkt;
+       struct calipso_map_cache_entry *entry;
+       struct calipso_map_cache_entry *prev_entry = NULL;
+       u32 hash;
+
+       if (!calipso_cache_enabled)
+               return -ENOENT;
+
+       hash = calipso_map_cache_hash(key, key_len);
+       bkt = hash & (CALIPSO_CACHE_BUCKETS - 1);
+       spin_lock_bh(&calipso_cache[bkt].lock);
+       list_for_each_entry(entry, &calipso_cache[bkt].list, list) {
+               if (entry->hash == hash &&
+                   entry->key_len == key_len &&
+                   memcmp(entry->key, key, key_len) == 0) {
+                       entry->activity += 1;
+                       atomic_inc(&entry->lsm_data->refcount);
+                       secattr->cache = entry->lsm_data;
+                       secattr->flags |= NETLBL_SECATTR_CACHE;
+                       secattr->type = NETLBL_NLTYPE_CALIPSO;
+                       if (!prev_entry) {
+                               spin_unlock_bh(&calipso_cache[bkt].lock);
+                               return 0;
+                       }
+
+                       if (prev_entry->activity > 0)
+                               prev_entry->activity -= 1;
+                       if (entry->activity > prev_entry->activity &&
+                           entry->activity - prev_entry->activity >
+                           CALIPSO_CACHE_REORDERLIMIT) {
+                               __list_del(entry->list.prev, entry->list.next);
+                               __list_add(&entry->list,
+                                          prev_entry->list.prev,
+                                          &prev_entry->list);
+                       }
+
+                       spin_unlock_bh(&calipso_cache[bkt].lock);
+                       return 0;
+               }
+               prev_entry = entry;
+       }
+       spin_unlock_bh(&calipso_cache[bkt].lock);
+
+       return -ENOENT;
+}
+
+/**
+ * calipso_cache_add - Add an entry to the CALIPSO cache
+ * @calipso_ptr: the CALIPSO option
+ * @secattr: the packet's security attributes
+ *
+ * Description:
+ * Add a new entry into the CALIPSO label mapping cache.  Add the new entry to
+ * head of the cache bucket's list, if the cache bucket is out of room remove
+ * the last entry in the list first.  It is important to note that there is
+ * currently no checking for duplicate keys.  Returns zero on success,
+ * negative values on failure.  The key stored starts at calipso_ptr + 2,
+ * i.e. the type and length bytes are not stored, this corresponds to
+ * calipso_ptr[1] bytes of data.
+ *
+ */
+static int calipso_cache_add(const unsigned char *calipso_ptr,
+                            const struct netlbl_lsm_secattr *secattr)
+{
+       int ret_val = -EPERM;
+       u32 bkt;
+       struct calipso_map_cache_entry *entry = NULL;
+       struct calipso_map_cache_entry *old_entry = NULL;
+       u32 calipso_ptr_len;
+
+       if (!calipso_cache_enabled || calipso_cache_bucketsize <= 0)
+               return 0;
+
+       calipso_ptr_len = calipso_ptr[1];
+
+       entry = kzalloc(sizeof(*entry), GFP_ATOMIC);
+       if (!entry)
+               return -ENOMEM;
+       entry->key = kmemdup(calipso_ptr + 2, calipso_ptr_len, GFP_ATOMIC);
+       if (!entry->key) {
+               ret_val = -ENOMEM;
+               goto cache_add_failure;
+       }
+       entry->key_len = calipso_ptr_len;
+       entry->hash = calipso_map_cache_hash(calipso_ptr, calipso_ptr_len);
+       atomic_inc(&secattr->cache->refcount);
+       entry->lsm_data = secattr->cache;
+
+       bkt = entry->hash & (CALIPSO_CACHE_BUCKETS - 1);
+       spin_lock_bh(&calipso_cache[bkt].lock);
+       if (calipso_cache[bkt].size < calipso_cache_bucketsize) {
+               list_add(&entry->list, &calipso_cache[bkt].list);
+               calipso_cache[bkt].size += 1;
+       } else {
+               old_entry = list_entry(calipso_cache[bkt].list.prev,
+                                      struct calipso_map_cache_entry, list);
+               list_del(&old_entry->list);
+               list_add(&entry->list, &calipso_cache[bkt].list);
+               calipso_cache_entry_free(old_entry);
+       }
+       spin_unlock_bh(&calipso_cache[bkt].lock);
+
+       return 0;
+
+cache_add_failure:
+       if (entry)
+               calipso_cache_entry_free(entry);
+       return ret_val;
+}
+
+/* DOI List Functions
+ */
+
+/**
+ * calipso_doi_search - Searches for a DOI definition
+ * @doi: the DOI to search for
+ *
+ * Description:
+ * Search the DOI definition list for a DOI definition with a DOI value that
+ * matches @doi.  The caller is responsible for calling rcu_read_[un]lock().
+ * Returns a pointer to the DOI definition on success and NULL on failure.
+ */
+static struct calipso_doi *calipso_doi_search(u32 doi)
+{
+       struct calipso_doi *iter;
+
+       list_for_each_entry_rcu(iter, &calipso_doi_list, list)
+               if (iter->doi == doi && atomic_read(&iter->refcount))
+                       return iter;
+       return NULL;
+}
+
+/**
+ * calipso_doi_add - Add a new DOI to the CALIPSO protocol engine
+ * @doi_def: the DOI structure
+ * @audit_info: NetLabel audit information
+ *
+ * Description:
+ * The caller defines a new DOI for use by the CALIPSO engine and calls this
+ * function to add it to the list of acceptable domains.  The caller must
+ * ensure that the mapping table specified in @doi_def->map meets all of the
+ * requirements of the mapping type (see calipso.h for details).  Returns
+ * zero on success and non-zero on failure.
+ *
+ */
+static int calipso_doi_add(struct calipso_doi *doi_def,
+                          struct netlbl_audit *audit_info)
+{
+       int ret_val = -EINVAL;
+       u32 doi;
+       u32 doi_type;
+       struct audit_buffer *audit_buf;
+
+       doi = doi_def->doi;
+       doi_type = doi_def->type;
+
+       if (doi_def->doi == CALIPSO_DOI_UNKNOWN)
+               goto doi_add_return;
+
+       atomic_set(&doi_def->refcount, 1);
+
+       spin_lock(&calipso_doi_list_lock);
+       if (calipso_doi_search(doi_def->doi)) {
+               spin_unlock(&calipso_doi_list_lock);
+               ret_val = -EEXIST;
+               goto doi_add_return;
+       }
+       list_add_tail_rcu(&doi_def->list, &calipso_doi_list);
+       spin_unlock(&calipso_doi_list_lock);
+       ret_val = 0;
+
+doi_add_return:
+       audit_buf = netlbl_audit_start(AUDIT_MAC_CALIPSO_ADD, audit_info);
+       if (audit_buf) {
+               const char *type_str;
+
+               switch (doi_type) {
+               case CALIPSO_MAP_PASS:
+                       type_str = "pass";
+                       break;
+               default:
+                       type_str = "(unknown)";
+               }
+               audit_log_format(audit_buf,
+                                " calipso_doi=%u calipso_type=%s res=%u",
+                                doi, type_str, ret_val == 0 ? 1 : 0);
+               audit_log_end(audit_buf);
+       }
+
+       return ret_val;
+}
+
+/**
+ * calipso_doi_free - Frees a DOI definition
+ * @doi_def: the DOI definition
+ *
+ * Description:
+ * This function frees all of the memory associated with a DOI definition.
+ *
+ */
+static void calipso_doi_free(struct calipso_doi *doi_def)
+{
+       kfree(doi_def);
+}
+
+/**
+ * calipso_doi_free_rcu - Frees a DOI definition via the RCU pointer
+ * @entry: the entry's RCU field
+ *
+ * Description:
+ * This function is designed to be used as a callback to the call_rcu()
+ * function so that the memory allocated to the DOI definition can be released
+ * safely.
+ *
+ */
+static void calipso_doi_free_rcu(struct rcu_head *entry)
+{
+       struct calipso_doi *doi_def;
+
+       doi_def = container_of(entry, struct calipso_doi, rcu);
+       calipso_doi_free(doi_def);
+}
+
+/**
+ * calipso_doi_remove - Remove an existing DOI from the CALIPSO protocol engine
+ * @doi: the DOI value
+ * @audit_secid: the LSM secid to use in the audit message
+ *
+ * Description:
+ * Removes a DOI definition from the CALIPSO engine.  The NetLabel routines will
+ * be called to release their own LSM domain mappings as well as our own
+ * domain list.  Returns zero on success and negative values on failure.
+ *
+ */
+static int calipso_doi_remove(u32 doi, struct netlbl_audit *audit_info)
+{
+       int ret_val;
+       struct calipso_doi *doi_def;
+       struct audit_buffer *audit_buf;
+
+       spin_lock(&calipso_doi_list_lock);
+       doi_def = calipso_doi_search(doi);
+       if (!doi_def) {
+               spin_unlock(&calipso_doi_list_lock);
+               ret_val = -ENOENT;
+               goto doi_remove_return;
+       }
+       if (!atomic_dec_and_test(&doi_def->refcount)) {
+               spin_unlock(&calipso_doi_list_lock);
+               ret_val = -EBUSY;
+               goto doi_remove_return;
+       }
+       list_del_rcu(&doi_def->list);
+       spin_unlock(&calipso_doi_list_lock);
+
+       call_rcu(&doi_def->rcu, calipso_doi_free_rcu);
+       ret_val = 0;
+
+doi_remove_return:
+       audit_buf = netlbl_audit_start(AUDIT_MAC_CALIPSO_DEL, audit_info);
+       if (audit_buf) {
+               audit_log_format(audit_buf,
+                                " calipso_doi=%u res=%u",
+                                doi, ret_val == 0 ? 1 : 0);
+               audit_log_end(audit_buf);
+       }
+
+       return ret_val;
+}
+
+/**
+ * calipso_doi_getdef - Returns a reference to a valid DOI definition
+ * @doi: the DOI value
+ *
+ * Description:
+ * Searches for a valid DOI definition and if one is found it is returned to
+ * the caller.  Otherwise NULL is returned.  The caller must ensure that
+ * calipso_doi_putdef() is called when the caller is done.
+ *
+ */
+static struct calipso_doi *calipso_doi_getdef(u32 doi)
+{
+       struct calipso_doi *doi_def;
+
+       rcu_read_lock();
+       doi_def = calipso_doi_search(doi);
+       if (!doi_def)
+               goto doi_getdef_return;
+       if (!atomic_inc_not_zero(&doi_def->refcount))
+               doi_def = NULL;
+
+doi_getdef_return:
+       rcu_read_unlock();
+       return doi_def;
+}
+
+/**
+ * calipso_doi_putdef - Releases a reference for the given DOI definition
+ * @doi_def: the DOI definition
+ *
+ * Description:
+ * Releases a DOI definition reference obtained from calipso_doi_getdef().
+ *
+ */
+static void calipso_doi_putdef(struct calipso_doi *doi_def)
+{
+       if (!doi_def)
+               return;
+
+       if (!atomic_dec_and_test(&doi_def->refcount))
+               return;
+       spin_lock(&calipso_doi_list_lock);
+       list_del_rcu(&doi_def->list);
+       spin_unlock(&calipso_doi_list_lock);
+
+       call_rcu(&doi_def->rcu, calipso_doi_free_rcu);
+}
+
+/**
+ * calipso_doi_walk - Iterate through the DOI definitions
+ * @skip_cnt: skip past this number of DOI definitions, updated
+ * @callback: callback for each DOI definition
+ * @cb_arg: argument for the callback function
+ *
+ * Description:
+ * Iterate over the DOI definition list, skipping the first @skip_cnt entries.
+ * For each entry call @callback, if @callback returns a negative value stop
+ * 'walking' through the list and return.  Updates the value in @skip_cnt upon
+ * return.  Returns zero on success, negative values on failure.
+ *
+ */
+static int calipso_doi_walk(u32 *skip_cnt,
+                           int (*callback)(struct calipso_doi *doi_def,
+                                           void *arg),
+                           void *cb_arg)
+{
+       int ret_val = -ENOENT;
+       u32 doi_cnt = 0;
+       struct calipso_doi *iter_doi;
+
+       rcu_read_lock();
+       list_for_each_entry_rcu(iter_doi, &calipso_doi_list, list)
+               if (atomic_read(&iter_doi->refcount) > 0) {
+                       if (doi_cnt++ < *skip_cnt)
+                               continue;
+                       ret_val = callback(iter_doi, cb_arg);
+                       if (ret_val < 0) {
+                               doi_cnt--;
+                               goto doi_walk_return;
+                       }
+               }
+
+doi_walk_return:
+       rcu_read_unlock();
+       *skip_cnt = doi_cnt;
+       return ret_val;
+}
+
+/**
+ * calipso_validate - Validate a CALIPSO option
+ * @skb: the packet
+ * @option: the start of the option
+ *
+ * Description:
+ * This routine is called to validate a CALIPSO option.
+ * If the option is valid then %true is returned, otherwise
+ * %false is returned.
+ *
+ * The caller should have already checked that the length of the
+ * option (including the TLV header) is >= 10 and that the catmap
+ * length is consistent with the option length.
+ *
+ * We leave checks on the level and categories to the socket layer.
+ */
+bool calipso_validate(const struct sk_buff *skb, const unsigned char *option)
+{
+       struct calipso_doi *doi_def;
+       bool ret_val;
+       u16 crc, len = option[1] + 2;
+       static const u8 zero[2];
+
+       /* The original CRC runs over the option including the TLV header
+        * with the CRC-16 field (at offset 8) zeroed out. */
+       crc = crc_ccitt(0xffff, option, 8);
+       crc = crc_ccitt(crc, zero, sizeof(zero));
+       if (len > 10)
+               crc = crc_ccitt(crc, option + 10, len - 10);
+       crc = ~crc;
+       if (option[8] != (crc & 0xff) || option[9] != ((crc >> 8) & 0xff))
+               return false;
+
+       rcu_read_lock();
+       doi_def = calipso_doi_search(get_unaligned_be32(option + 2));
+       ret_val = !!doi_def;
+       rcu_read_unlock();
+
+       return ret_val;
+}
+
+/**
+ * calipso_map_cat_hton - Perform a category mapping from host to network
+ * @doi_def: the DOI definition
+ * @secattr: the security attributes
+ * @net_cat: the zero'd out category bitmap in network/CALIPSO format
+ * @net_cat_len: the length of the CALIPSO bitmap in bytes
+ *
+ * Description:
+ * Perform a label mapping to translate a local MLS category bitmap to the
+ * correct CALIPSO bitmap using the given DOI definition.  Returns the minimum
+ * size in bytes of the network bitmap on success, negative values otherwise.
+ *
+ */
+static int calipso_map_cat_hton(const struct calipso_doi *doi_def,
+                               const struct netlbl_lsm_secattr *secattr,
+                               unsigned char *net_cat,
+                               u32 net_cat_len)
+{
+       int spot = -1;
+       u32 net_spot_max = 0;
+       u32 net_clen_bits = net_cat_len * 8;
+
+       for (;;) {
+               spot = netlbl_catmap_walk(secattr->attr.mls.cat,
+                                         spot + 1);
+               if (spot < 0)
+                       break;
+               if (spot >= net_clen_bits)
+                       return -ENOSPC;
+               netlbl_bitmap_setbit(net_cat, spot, 1);
+
+               if (spot > net_spot_max)
+                       net_spot_max = spot;
+       }
+
+       return (net_spot_max / 32 + 1) * 4;
+}
+
+/**
+ * calipso_map_cat_ntoh - Perform a category mapping from network to host
+ * @doi_def: the DOI definition
+ * @net_cat: the category bitmap in network/CALIPSO format
+ * @net_cat_len: the length of the CALIPSO bitmap in bytes
+ * @secattr: the security attributes
+ *
+ * Description:
+ * Perform a label mapping to translate a CALIPSO bitmap to the correct local
+ * MLS category bitmap using the given DOI definition.  Returns zero on
+ * success, negative values on failure.
+ *
+ */
+static int calipso_map_cat_ntoh(const struct calipso_doi *doi_def,
+                               const unsigned char *net_cat,
+                               u32 net_cat_len,
+                               struct netlbl_lsm_secattr *secattr)
+{
+       int ret_val;
+       int spot = -1;
+       u32 net_clen_bits = net_cat_len * 8;
+
+       for (;;) {
+               spot = netlbl_bitmap_walk(net_cat,
+                                         net_clen_bits,
+                                         spot + 1,
+                                         1);
+               if (spot < 0) {
+                       if (spot == -2)
+                               return -EFAULT;
+                       return 0;
+               }
+
+               ret_val = netlbl_catmap_setbit(&secattr->attr.mls.cat,
+                                              spot,
+                                              GFP_ATOMIC);
+               if (ret_val != 0)
+                       return ret_val;
+       }
+
+       return -EINVAL;
+}
+
+/**
+ * calipso_pad_write - Writes pad bytes in TLV format
+ * @buf: the buffer
+ * @offset: offset from start of buffer to write padding
+ * @count: number of pad bytes to write
+ *
+ * Description:
+ * Write @count bytes of TLV padding into @buffer starting at offset @offset.
+ * @count should be less than 8 - see RFC 4942.
+ *
+ */
+static int calipso_pad_write(unsigned char *buf, unsigned int offset,
+                            unsigned int count)
+{
+       if (WARN_ON_ONCE(count >= 8))
+               return -EINVAL;
+
+       switch (count) {
+       case 0:
+               break;
+       case 1:
+               buf[offset] = IPV6_TLV_PAD1;
+               break;
+       default:
+               buf[offset] = IPV6_TLV_PADN;
+               buf[offset + 1] = count - 2;
+               if (count > 2)
+                       memset(buf + offset + 2, 0, count - 2);
+               break;
+       }
+       return 0;
+}
+
+/**
+ * calipso_genopt - Generate a CALIPSO option
+ * @buf: the option buffer
+ * @start: offset from which to write
+ * @buf_len: the size of opt_buf
+ * @doi_def: the CALIPSO DOI to use
+ * @secattr: the security attributes
+ *
+ * Description:
+ * Generate a CALIPSO option using the DOI definition and security attributes
+ * passed to the function. This also generates upto three bytes of leading
+ * padding that ensures that the option is 4n + 2 aligned.  It returns the
+ * number of bytes written (including any initial padding).
+ */
+static int calipso_genopt(unsigned char *buf, u32 start, u32 buf_len,
+                         const struct calipso_doi *doi_def,
+                         const struct netlbl_lsm_secattr *secattr)
+{
+       int ret_val;
+       u32 len, pad;
+       u16 crc;
+       static const unsigned char padding[4] = {2, 1, 0, 3};
+       unsigned char *calipso;
+
+       /* CALIPSO has 4n + 2 alignment */
+       pad = padding[start & 3];
+       if (buf_len <= start + pad + CALIPSO_HDR_LEN)
+               return -ENOSPC;
+
+       if ((secattr->flags & NETLBL_SECATTR_MLS_LVL) == 0)
+               return -EPERM;
+
+       len = CALIPSO_HDR_LEN;
+
+       if (secattr->flags & NETLBL_SECATTR_MLS_CAT) {
+               ret_val = calipso_map_cat_hton(doi_def,
+                                              secattr,
+                                              buf + start + pad + len,
+                                              buf_len - start - pad - len);
+               if (ret_val < 0)
+                       return ret_val;
+               len += ret_val;
+       }
+
+       calipso_pad_write(buf, start, pad);
+       calipso = buf + start + pad;
+
+       calipso[0] = IPV6_TLV_CALIPSO;
+       calipso[1] = len - 2;
+       *(__be32 *)(calipso + 2) = htonl(doi_def->doi);
+       calipso[6] = (len - CALIPSO_HDR_LEN) / 4;
+       calipso[7] = secattr->attr.mls.lvl,
+       crc = ~crc_ccitt(0xffff, calipso, len);
+       calipso[8] = crc & 0xff;
+       calipso[9] = (crc >> 8) & 0xff;
+       return pad + len;
+}
+
+/* Hop-by-hop hdr helper functions
+ */
+
+/**
+ * calipso_opt_update - Replaces socket's hop options with a new set
+ * @sk: the socket
+ * @hop: new hop options
+ *
+ * Description:
+ * Replaces @sk's hop options with @hop.  @hop may be NULL to leave
+ * the socket with no hop options.
+ *
+ */
+static int calipso_opt_update(struct sock *sk, struct ipv6_opt_hdr *hop)
+{
+       struct ipv6_txoptions *old = txopt_get(inet6_sk(sk)), *txopts;
+
+       txopts = ipv6_renew_options_kern(sk, old, IPV6_HOPOPTS,
+                                        hop, hop ? ipv6_optlen(hop) : 0);
+       txopt_put(old);
+       if (IS_ERR(txopts))
+               return PTR_ERR(txopts);
+
+       txopts = ipv6_update_options(sk, txopts);
+       if (txopts) {
+               atomic_sub(txopts->tot_len, &sk->sk_omem_alloc);
+               txopt_put(txopts);
+       }
+
+       return 0;
+}
+
+/**
+ * calipso_tlv_len - Returns the length of the TLV
+ * @opt: the option header
+ * @offset: offset of the TLV within the header
+ *
+ * Description:
+ * Returns the length of the TLV option at offset @offset within
+ * the option header @opt.  Checks that the entire TLV fits inside
+ * the option header, returns a negative value if this is not the case.
+ */
+static int calipso_tlv_len(struct ipv6_opt_hdr *opt, unsigned int offset)
+{
+       unsigned char *tlv = (unsigned char *)opt;
+       unsigned int opt_len = ipv6_optlen(opt), tlv_len;
+
+       if (offset < sizeof(*opt) || offset >= opt_len)
+               return -EINVAL;
+       if (tlv[offset] == IPV6_TLV_PAD1)
+               return 1;
+       if (offset + 1 >= opt_len)
+               return -EINVAL;
+       tlv_len = tlv[offset + 1] + 2;
+       if (offset + tlv_len > opt_len)
+               return -EINVAL;
+       return tlv_len;
+}
+
+/**
+ * calipso_opt_find - Finds the CALIPSO option in an IPv6 hop options header
+ * @hop: the hop options header
+ * @start: on return holds the offset of any leading padding
+ * @end: on return holds the offset of the first non-pad TLV after CALIPSO
+ *
+ * Description:
+ * Finds the space occupied by a CALIPSO option (including any leading and
+ * trailing padding).
+ *
+ * If a CALIPSO option exists set @start and @end to the
+ * offsets within @hop of the start of padding before the first
+ * CALIPSO option and the end of padding after the first CALIPSO
+ * option.  In this case the function returns 0.
+ *
+ * In the absence of a CALIPSO option, @start and @end will be
+ * set to the start and end of any trailing padding in the header.
+ * This is useful when appending a new option, as the caller may want
+ * to overwrite some of this padding.  In this case the function will
+ * return -ENOENT.
+ */
+static int calipso_opt_find(struct ipv6_opt_hdr *hop, unsigned int *start,
+                           unsigned int *end)
+{
+       int ret_val = -ENOENT, tlv_len;
+       unsigned int opt_len, offset, offset_s = 0, offset_e = 0;
+       unsigned char *opt = (unsigned char *)hop;
+
+       opt_len = ipv6_optlen(hop);
+       offset = sizeof(*hop);
+
+       while (offset < opt_len) {
+               tlv_len = calipso_tlv_len(hop, offset);
+               if (tlv_len < 0)
+                       return tlv_len;
+
+               switch (opt[offset]) {
+               case IPV6_TLV_PAD1:
+               case IPV6_TLV_PADN:
+                       if (offset_e)
+                               offset_e = offset;
+                       break;
+               case IPV6_TLV_CALIPSO:
+                       ret_val = 0;
+                       offset_e = offset;
+                       break;
+               default:
+                       if (offset_e == 0)
+                               offset_s = offset;
+                       else
+                               goto out;
+               }
+               offset += tlv_len;
+       }
+
+out:
+       if (offset_s)
+               *start = offset_s + calipso_tlv_len(hop, offset_s);
+       else
+               *start = sizeof(*hop);
+       if (offset_e)
+               *end = offset_e + calipso_tlv_len(hop, offset_e);
+       else
+               *end = opt_len;
+
+       return ret_val;
+}
+
+/**
+ * calipso_opt_insert - Inserts a CALIPSO option into an IPv6 hop opt hdr
+ * @hop: the original hop options header
+ * @doi_def: the CALIPSO DOI to use
+ * @secattr: the specific security attributes of the socket
+ *
+ * Description:
+ * Creates a new hop options header based on @hop with a
+ * CALIPSO option added to it.  If @hop already contains a CALIPSO
+ * option this is overwritten, otherwise the new option is appended
+ * after any existing options.  If @hop is NULL then the new header
+ * will contain just the CALIPSO option and any needed padding.
+ *
+ */
+static struct ipv6_opt_hdr *
+calipso_opt_insert(struct ipv6_opt_hdr *hop,
+                  const struct calipso_doi *doi_def,
+                  const struct netlbl_lsm_secattr *secattr)
+{
+       unsigned int start, end, buf_len, pad, hop_len;
+       struct ipv6_opt_hdr *new;
+       int ret_val;
+
+       if (hop) {
+               hop_len = ipv6_optlen(hop);
+               ret_val = calipso_opt_find(hop, &start, &end);
+               if (ret_val && ret_val != -ENOENT)
+                       return ERR_PTR(ret_val);
+       } else {
+               hop_len = 0;
+               start = sizeof(*hop);
+               end = 0;
+       }
+
+       buf_len = hop_len + start - end + CALIPSO_OPT_LEN_MAX_WITH_PAD;
+       new = kzalloc(buf_len, GFP_ATOMIC);
+       if (!new)
+               return ERR_PTR(-ENOMEM);
+
+       if (start > sizeof(*hop))
+               memcpy(new, hop, start);
+       ret_val = calipso_genopt((unsigned char *)new, start, buf_len, doi_def,
+                                secattr);
+       if (ret_val < 0)
+               return ERR_PTR(ret_val);
+
+       buf_len = start + ret_val;
+       /* At this point buf_len aligns to 4n, so (buf_len & 4) pads to 8n */
+       pad = ((buf_len & 4) + (end & 7)) & 7;
+       calipso_pad_write((unsigned char *)new, buf_len, pad);
+       buf_len += pad;
+
+       if (end != hop_len) {
+               memcpy((char *)new + buf_len, (char *)hop + end, hop_len - end);
+               buf_len += hop_len - end;
+       }
+       new->nexthdr = 0;
+       new->hdrlen = buf_len / 8 - 1;
+
+       return new;
+}
+
+/**
+ * calipso_opt_del - Removes the CALIPSO option from an option header
+ * @hop: the original header
+ * @new: the new header
+ *
+ * Description:
+ * Creates a new header based on @hop without any CALIPSO option.  If @hop
+ * doesn't contain a CALIPSO option it returns -ENOENT.  If @hop contains
+ * no other non-padding options, it returns zero with @new set to NULL.
+ * Otherwise it returns zero, creates a new header without the CALIPSO
+ * option (and removing as much padding as possible) and returns with
+ * @new set to that header.
+ *
+ */
+static int calipso_opt_del(struct ipv6_opt_hdr *hop,
+                          struct ipv6_opt_hdr **new)
+{
+       int ret_val;
+       unsigned int start, end, delta, pad, hop_len;
+
+       ret_val = calipso_opt_find(hop, &start, &end);
+       if (ret_val)
+               return ret_val;
+
+       hop_len = ipv6_optlen(hop);
+       if (start == sizeof(*hop) && end == hop_len) {
+               /* There's no other option in the header so return NULL */
+               *new = NULL;
+               return 0;
+       }
+
+       delta = (end - start) & ~7;
+       *new = kzalloc(hop_len - delta, GFP_ATOMIC);
+       if (!*new)
+               return -ENOMEM;
+
+       memcpy(*new, hop, start);
+       (*new)->hdrlen -= delta / 8;
+       pad = (end - start) & 7;
+       calipso_pad_write((unsigned char *)*new, start, pad);
+       if (end != hop_len)
+               memcpy((char *)*new + start + pad, (char *)hop + end,
+                      hop_len - end);
+
+       return 0;
+}
+
+/**
+ * calipso_opt_getattr - Get the security attributes from a memory block
+ * @calipso: the CALIPSO option
+ * @secattr: the security attributes
+ *
+ * Description:
+ * Inspect @calipso and return the security attributes in @secattr.
+ * Returns zero on success and negative values on failure.
+ *
+ */
+static int calipso_opt_getattr(const unsigned char *calipso,
+                              struct netlbl_lsm_secattr *secattr)
+{
+       int ret_val = -ENOMSG;
+       u32 doi, len = calipso[1], cat_len = calipso[6] * 4;
+       struct calipso_doi *doi_def;
+
+       if (cat_len + 8 > len)
+               return -EINVAL;
+
+       if (calipso_cache_check(calipso + 2, calipso[1], secattr) == 0)
+               return 0;
+
+       doi = get_unaligned_be32(calipso + 2);
+       rcu_read_lock();
+       doi_def = calipso_doi_search(doi);
+       if (!doi_def)
+               goto getattr_return;
+
+       secattr->attr.mls.lvl = calipso[7];
+       secattr->flags |= NETLBL_SECATTR_MLS_LVL;
+
+       if (cat_len) {
+               ret_val = calipso_map_cat_ntoh(doi_def,
+                                              calipso + 10,
+                                              cat_len,
+                                              secattr);
+               if (ret_val != 0) {
+                       netlbl_catmap_free(secattr->attr.mls.cat);
+                       goto getattr_return;
+               }
+
+               secattr->flags |= NETLBL_SECATTR_MLS_CAT;
+       }
+
+       secattr->type = NETLBL_NLTYPE_CALIPSO;
+
+getattr_return:
+       rcu_read_unlock();
+       return ret_val;
+}
+
+/* sock functions.
+ */
+
+/**
+ * calipso_sock_getattr - Get the security attributes from a sock
+ * @sk: the sock
+ * @secattr: the security attributes
+ *
+ * Description:
+ * Query @sk to see if there is a CALIPSO option attached to the sock and if
+ * there is return the CALIPSO security attributes in @secattr.  This function
+ * requires that @sk be locked, or privately held, but it does not do any
+ * locking itself.  Returns zero on success and negative values on failure.
+ *
+ */
+static int calipso_sock_getattr(struct sock *sk,
+                               struct netlbl_lsm_secattr *secattr)
+{
+       struct ipv6_opt_hdr *hop;
+       int opt_len, len, ret_val = -ENOMSG, offset;
+       unsigned char *opt;
+       struct ipv6_txoptions *txopts = txopt_get(inet6_sk(sk));
+
+       if (!txopts || !txopts->hopopt)
+               goto done;
+
+       hop = txopts->hopopt;
+       opt = (unsigned char *)hop;
+       opt_len = ipv6_optlen(hop);
+       offset = sizeof(*hop);
+       while (offset < opt_len) {
+               len = calipso_tlv_len(hop, offset);
+               if (len < 0) {
+                       ret_val = len;
+                       goto done;
+               }
+               switch (opt[offset]) {
+               case IPV6_TLV_CALIPSO:
+                       if (len < CALIPSO_HDR_LEN)
+                               ret_val = -EINVAL;
+                       else
+                               ret_val = calipso_opt_getattr(&opt[offset],
+                                                             secattr);
+                       goto done;
+               default:
+                       offset += len;
+                       break;
+               }
+       }
+done:
+       txopt_put(txopts);
+       return ret_val;
+}
+
+/**
+ * calipso_sock_setattr - Add a CALIPSO option to a socket
+ * @sk: the socket
+ * @doi_def: the CALIPSO DOI to use
+ * @secattr: the specific security attributes of the socket
+ *
+ * Description:
+ * Set the CALIPSO option on the given socket using the DOI definition and
+ * security attributes passed to the function.  This function requires
+ * exclusive access to @sk, which means it either needs to be in the
+ * process of being created or locked.  Returns zero on success and negative
+ * values on failure.
+ *
+ */
+static int calipso_sock_setattr(struct sock *sk,
+                               const struct calipso_doi *doi_def,
+                               const struct netlbl_lsm_secattr *secattr)
+{
+       int ret_val;
+       struct ipv6_opt_hdr *old, *new;
+       struct ipv6_txoptions *txopts = txopt_get(inet6_sk(sk));
+
+       old = NULL;
+       if (txopts)
+               old = txopts->hopopt;
+
+       new = calipso_opt_insert(old, doi_def, secattr);
+       txopt_put(txopts);
+       if (IS_ERR(new))
+               return PTR_ERR(new);
+
+       ret_val = calipso_opt_update(sk, new);
+
+       kfree(new);
+       return ret_val;
+}
+
+/**
+ * calipso_sock_delattr - Delete the CALIPSO option from a socket
+ * @sk: the socket
+ *
+ * Description:
+ * Removes the CALIPSO option from a socket, if present.
+ *
+ */
+static void calipso_sock_delattr(struct sock *sk)
+{
+       struct ipv6_opt_hdr *new_hop;
+       struct ipv6_txoptions *txopts = txopt_get(inet6_sk(sk));
+
+       if (!txopts || !txopts->hopopt)
+               goto done;
+
+       if (calipso_opt_del(txopts->hopopt, &new_hop))
+               goto done;
+
+       calipso_opt_update(sk, new_hop);
+       kfree(new_hop);
+
+done:
+       txopt_put(txopts);
+}
+
+/* request sock functions.
+ */
+
+/**
+ * calipso_req_setattr - Add a CALIPSO option to a connection request socket
+ * @req: the connection request socket
+ * @doi_def: the CALIPSO DOI to use
+ * @secattr: the specific security attributes of the socket
+ *
+ * Description:
+ * Set the CALIPSO option on the given socket using the DOI definition and
+ * security attributes passed to the function.  Returns zero on success and
+ * negative values on failure.
+ *
+ */
+static int calipso_req_setattr(struct request_sock *req,
+                              const struct calipso_doi *doi_def,
+                              const struct netlbl_lsm_secattr *secattr)
+{
+       struct ipv6_txoptions *txopts;
+       struct inet_request_sock *req_inet = inet_rsk(req);
+       struct ipv6_opt_hdr *old, *new;
+       struct sock *sk = sk_to_full_sk(req_to_sk(req));
+
+       if (req_inet->ipv6_opt && req_inet->ipv6_opt->hopopt)
+               old = req_inet->ipv6_opt->hopopt;
+       else
+               old = NULL;
+
+       new = calipso_opt_insert(old, doi_def, secattr);
+       if (IS_ERR(new))
+               return PTR_ERR(new);
+
+       txopts = ipv6_renew_options_kern(sk, req_inet->ipv6_opt, IPV6_HOPOPTS,
+                                        new, new ? ipv6_optlen(new) : 0);
+
+       kfree(new);
+
+       if (IS_ERR(txopts))
+               return PTR_ERR(txopts);
+
+       txopts = xchg(&req_inet->ipv6_opt, txopts);
+       if (txopts) {
+               atomic_sub(txopts->tot_len, &sk->sk_omem_alloc);
+               txopt_put(txopts);
+       }
+
+       return 0;
+}
+
+/**
+ * calipso_req_delattr - Delete the CALIPSO option from a request socket
+ * @reg: the request socket
+ *
+ * Description:
+ * Removes the CALIPSO option from a request socket, if present.
+ *
+ */
+static void calipso_req_delattr(struct request_sock *req)
+{
+       struct inet_request_sock *req_inet = inet_rsk(req);
+       struct ipv6_opt_hdr *new;
+       struct ipv6_txoptions *txopts;
+       struct sock *sk = sk_to_full_sk(req_to_sk(req));
+
+       if (!req_inet->ipv6_opt || !req_inet->ipv6_opt->hopopt)
+               return;
+
+       if (calipso_opt_del(req_inet->ipv6_opt->hopopt, &new))
+               return; /* Nothing to do */
+
+       txopts = ipv6_renew_options_kern(sk, req_inet->ipv6_opt, IPV6_HOPOPTS,
+                                        new, new ? ipv6_optlen(new) : 0);
+
+       if (!IS_ERR(txopts)) {
+               txopts = xchg(&req_inet->ipv6_opt, txopts);
+               if (txopts) {
+                       atomic_sub(txopts->tot_len, &sk->sk_omem_alloc);
+                       txopt_put(txopts);
+               }
+       }
+       kfree(new);
+}
+
+/* skbuff functions.
+ */
+
+/**
+ * calipso_skbuff_optptr - Find the CALIPSO option in the packet
+ * @skb: the packet
+ *
+ * Description:
+ * Parse the packet's IP header looking for a CALIPSO option.  Returns a pointer
+ * to the start of the CALIPSO option on success, NULL if one if not found.
+ *
+ */
+static unsigned char *calipso_skbuff_optptr(const struct sk_buff *skb)
+{
+       const struct ipv6hdr *ip6_hdr = ipv6_hdr(skb);
+       int offset;
+
+       if (ip6_hdr->nexthdr != NEXTHDR_HOP)
+               return NULL;
+
+       offset = ipv6_find_tlv(skb, sizeof(*ip6_hdr), IPV6_TLV_CALIPSO);
+       if (offset >= 0)
+               return (unsigned char *)ip6_hdr + offset;
+
+       return NULL;
+}
+
+/**
+ * calipso_skbuff_setattr - Set the CALIPSO option on a packet
+ * @skb: the packet
+ * @doi_def: the CALIPSO DOI to use
+ * @secattr: the security attributes
+ *
+ * Description:
+ * Set the CALIPSO option on the given packet based on the security attributes.
+ * Returns a pointer to the IP header on success and NULL on failure.
+ *
+ */
+static int calipso_skbuff_setattr(struct sk_buff *skb,
+                                 const struct calipso_doi *doi_def,
+                                 const struct netlbl_lsm_secattr *secattr)
+{
+       int ret_val;
+       struct ipv6hdr *ip6_hdr;
+       struct ipv6_opt_hdr *hop;
+       unsigned char buf[CALIPSO_MAX_BUFFER];
+       int len_delta, new_end, pad;
+       unsigned int start, end;
+
+       ip6_hdr = ipv6_hdr(skb);
+       if (ip6_hdr->nexthdr == NEXTHDR_HOP) {
+               hop = (struct ipv6_opt_hdr *)(ip6_hdr + 1);
+               ret_val = calipso_opt_find(hop, &start, &end);
+               if (ret_val && ret_val != -ENOENT)
+                       return ret_val;
+       } else {
+               start = 0;
+               end = 0;
+       }
+
+       memset(buf, 0, sizeof(buf));
+       ret_val = calipso_genopt(buf, start & 3, sizeof(buf), doi_def, secattr);
+       if (ret_val < 0)
+               return ret_val;
+
+       new_end = start + ret_val;
+       /* At this point new_end aligns to 4n, so (new_end & 4) pads to 8n */
+       pad = ((new_end & 4) + (end & 7)) & 7;
+       len_delta = new_end - (int)end + pad;
+       ret_val = skb_cow(skb, skb_headroom(skb) + len_delta);
+       if (ret_val < 0)
+               return ret_val;
+
+       if (len_delta) {
+               if (len_delta > 0)
+                       skb_push(skb, len_delta);
+               else
+                       skb_pull(skb, -len_delta);
+               memmove((char *)ip6_hdr - len_delta, ip6_hdr,
+                       sizeof(*ip6_hdr) + start);
+               skb_reset_network_header(skb);
+               ip6_hdr = ipv6_hdr(skb);
+       }
+
+       hop = (struct ipv6_opt_hdr *)(ip6_hdr + 1);
+       if (start == 0) {
+               struct ipv6_opt_hdr *new_hop = (struct ipv6_opt_hdr *)buf;
+
+               new_hop->nexthdr = ip6_hdr->nexthdr;
+               new_hop->hdrlen = len_delta / 8 - 1;
+               ip6_hdr->nexthdr = NEXTHDR_HOP;
+       } else {
+               hop->hdrlen += len_delta / 8;
+       }
+       memcpy((char *)hop + start, buf + (start & 3), new_end - start);
+       calipso_pad_write((unsigned char *)hop, new_end, pad);
+
+       return 0;
+}
+
+/**
+ * calipso_skbuff_delattr - Delete any CALIPSO options from a packet
+ * @skb: the packet
+ *
+ * Description:
+ * Removes any and all CALIPSO options from the given packet.  Returns zero on
+ * success, negative values on failure.
+ *
+ */
+static int calipso_skbuff_delattr(struct sk_buff *skb)
+{
+       int ret_val;
+       struct ipv6hdr *ip6_hdr;
+       struct ipv6_opt_hdr *old_hop;
+       u32 old_hop_len, start = 0, end = 0, delta, size, pad;
+
+       if (!calipso_skbuff_optptr(skb))
+               return 0;
+
+       /* since we are changing the packet we should make a copy */
+       ret_val = skb_cow(skb, skb_headroom(skb));
+       if (ret_val < 0)
+               return ret_val;
+
+       ip6_hdr = ipv6_hdr(skb);
+       old_hop = (struct ipv6_opt_hdr *)(ip6_hdr + 1);
+       old_hop_len = ipv6_optlen(old_hop);
+
+       ret_val = calipso_opt_find(old_hop, &start, &end);
+       if (ret_val)
+               return ret_val;
+
+       if (start == sizeof(*old_hop) && end == old_hop_len) {
+               /* There's no other option in the header so we delete
+                * the whole thing. */
+               delta = old_hop_len;
+               size = sizeof(*ip6_hdr);
+               ip6_hdr->nexthdr = old_hop->nexthdr;
+       } else {
+               delta = (end - start) & ~7;
+               if (delta)
+                       old_hop->hdrlen -= delta / 8;
+               pad = (end - start) & 7;
+               size = sizeof(*ip6_hdr) + start + pad;
+               calipso_pad_write((unsigned char *)old_hop, start, pad);
+       }
+
+       if (delta) {
+               skb_pull(skb, delta);
+               memmove((char *)ip6_hdr + delta, ip6_hdr, size);
+               skb_reset_network_header(skb);
+       }
+
+       return 0;
+}
+
+static const struct netlbl_calipso_ops ops = {
+       .doi_add          = calipso_doi_add,
+       .doi_free         = calipso_doi_free,
+       .doi_remove       = calipso_doi_remove,
+       .doi_getdef       = calipso_doi_getdef,
+       .doi_putdef       = calipso_doi_putdef,
+       .doi_walk         = calipso_doi_walk,
+       .sock_getattr     = calipso_sock_getattr,
+       .sock_setattr     = calipso_sock_setattr,
+       .sock_delattr     = calipso_sock_delattr,
+       .req_setattr      = calipso_req_setattr,
+       .req_delattr      = calipso_req_delattr,
+       .opt_getattr      = calipso_opt_getattr,
+       .skbuff_optptr    = calipso_skbuff_optptr,
+       .skbuff_setattr   = calipso_skbuff_setattr,
+       .skbuff_delattr   = calipso_skbuff_delattr,
+       .cache_invalidate = calipso_cache_invalidate,
+       .cache_add        = calipso_cache_add
+};
+
+/**
+ * calipso_init - Initialize the CALIPSO module
+ *
+ * Description:
+ * Initialize the CALIPSO module and prepare it for use.  Returns zero on
+ * success and negative values on failure.
+ *
+ */
+int __init calipso_init(void)
+{
+       int ret_val;
+
+       ret_val = calipso_cache_init();
+       if (!ret_val)
+               netlbl_calipso_ops_register(&ops);
+       return ret_val;
+}
+
+void calipso_exit(void)
+{
+       netlbl_calipso_ops_register(NULL);
+       calipso_cache_invalidate();
+       kfree(calipso_cache);
+}
index 8de5dd7aaa05cf50aede2fb7255215df527349d7..139ceb68bd3705b5bc4cedb0c5a1e39fa59fc6b8 100644 (file)
@@ -43,6 +43,7 @@
 #include <net/ndisc.h>
 #include <net/ip6_route.h>
 #include <net/addrconf.h>
+#include <net/calipso.h>
 #if IS_ENABLED(CONFIG_IPV6_MIP6)
 #include <net/xfrm.h>
 #endif
@@ -603,6 +604,28 @@ drop:
        return false;
 }
 
+/* CALIPSO RFC 5570 */
+
+static bool ipv6_hop_calipso(struct sk_buff *skb, int optoff)
+{
+       const unsigned char *nh = skb_network_header(skb);
+
+       if (nh[optoff + 1] < 8)
+               goto drop;
+
+       if (nh[optoff + 6] * 4 + 8 > nh[optoff + 1])
+               goto drop;
+
+       if (!calipso_validate(skb, nh + optoff))
+               goto drop;
+
+       return true;
+
+drop:
+       kfree_skb(skb);
+       return false;
+}
+
 static const struct tlvtype_proc tlvprochopopt_lst[] = {
        {
                .type   = IPV6_TLV_ROUTERALERT,
@@ -612,6 +635,10 @@ static const struct tlvtype_proc tlvprochopopt_lst[] = {
                .type   = IPV6_TLV_JUMBO,
                .func   = ipv6_hop_jumbo,
        },
+       {
+               .type   = IPV6_TLV_CALIPSO,
+               .func   = ipv6_hop_calipso,
+       },
        { -1, }
 };
 
@@ -758,6 +785,27 @@ static int ipv6_renew_option(void *ohdr,
        return 0;
 }
 
+/**
+ * ipv6_renew_options - replace a specific ext hdr with a new one.
+ *
+ * @sk: sock from which to allocate memory
+ * @opt: original options
+ * @newtype: option type to replace in @opt
+ * @newopt: new option of type @newtype to replace (user-mem)
+ * @newoptlen: length of @newopt
+ *
+ * Returns a new set of options which is a copy of @opt with the
+ * option type @newtype replaced with @newopt.
+ *
+ * @opt may be NULL, in which case a new set of options is returned
+ * containing just @newopt.
+ *
+ * @newopt may be NULL, in which case the specified option type is
+ * not copied into the new set of options.
+ *
+ * The new set of options is allocated from the socket option memory
+ * buffer of @sk.
+ */
 struct ipv6_txoptions *
 ipv6_renew_options(struct sock *sk, struct ipv6_txoptions *opt,
                   int newtype,
@@ -830,6 +878,34 @@ out:
        return ERR_PTR(err);
 }
 
+/**
+ * ipv6_renew_options_kern - replace a specific ext hdr with a new one.
+ *
+ * @sk: sock from which to allocate memory
+ * @opt: original options
+ * @newtype: option type to replace in @opt
+ * @newopt: new option of type @newtype to replace (kernel-mem)
+ * @newoptlen: length of @newopt
+ *
+ * See ipv6_renew_options().  The difference is that @newopt is
+ * kernel memory, rather than user memory.
+ */
+struct ipv6_txoptions *
+ipv6_renew_options_kern(struct sock *sk, struct ipv6_txoptions *opt,
+                       int newtype, struct ipv6_opt_hdr *newopt,
+                       int newoptlen)
+{
+       struct ipv6_txoptions *ret_val;
+       const mm_segment_t old_fs = get_fs();
+
+       set_fs(KERNEL_DS);
+       ret_val = ipv6_renew_options(sk, opt, newtype,
+                                    (struct ipv6_opt_hdr __user *)newopt,
+                                    newoptlen);
+       set_fs(old_fs);
+       return ret_val;
+}
+
 struct ipv6_txoptions *ipv6_fixup_options(struct ipv6_txoptions *opt_space,
                                          struct ipv6_txoptions *opt)
 {
index 9508a20fbf61432f561202edbe40b59e63c3489e..305e2ed730bf42438190ad2fd7ae9ac9b4eed98f 100644 (file)
@@ -112,7 +112,7 @@ int ipv6_skip_exthdr(const struct sk_buff *skb, int start, u8 *nexthdrp,
 }
 EXPORT_SYMBOL(ipv6_skip_exthdr);
 
-int ipv6_find_tlv(struct sk_buff *skb, int offset, int type)
+int ipv6_find_tlv(const struct sk_buff *skb, int offset, int type)
 {
        const unsigned char *nh = skb_network_header(skb);
        int packet_len = skb_tail_pointer(skb) - skb_network_header(skb);
index a9895e15ee9c270f13842121b7dc66ed97a71135..5330262ab673c022fbf700d22782a74ccd1494fe 100644 (file)
@@ -98,7 +98,6 @@ int ip6_ra_control(struct sock *sk, int sel)
        return 0;
 }
 
-static
 struct ipv6_txoptions *ipv6_update_options(struct sock *sk,
                                           struct ipv6_txoptions *opt)
 {
index 45243bbe52536d523a7aff0b636a72b8f2e3daf5..69c50e737c5462384a4c6a82b500c96621b53392 100644 (file)
@@ -15,6 +15,9 @@
 #include <net/ipv6.h>
 #include <net/addrconf.h>
 #include <net/inet_frag.h>
+#ifdef CONFIG_NETLABEL
+#include <net/calipso.h>
+#endif
 
 static int one = 1;
 static int auto_flowlabels_min;
@@ -106,6 +109,22 @@ static struct ctl_table ipv6_rotable[] = {
                .proc_handler   = proc_dointvec_minmax,
                .extra1         = &one
        },
+#ifdef CONFIG_NETLABEL
+       {
+               .procname       = "calipso_cache_enable",
+               .data           = &calipso_cache_enabled,
+               .maxlen         = sizeof(int),
+               .mode           = 0644,
+               .proc_handler   = proc_dointvec,
+       },
+       {
+               .procname       = "calipso_cache_bucket_size",
+               .data           = &calipso_cache_bucketsize,
+               .maxlen         = sizeof(int),
+               .mode           = 0644,
+               .proc_handler   = proc_dointvec,
+       },
+#endif /* CONFIG_NETLABEL */
        { }
 };
 
index 79e33e02f11accfd5a6c8cf2751df0a8793ab016..818b3b5cd490cbacf1fae9163cab967263fc60a2 100644 (file)
@@ -443,6 +443,7 @@ static int tcp_v6_send_synack(const struct sock *sk, struct dst_entry *dst,
 {
        struct inet_request_sock *ireq = inet_rsk(req);
        struct ipv6_pinfo *np = inet6_sk(sk);
+       struct ipv6_txoptions *opt;
        struct flowi6 *fl6 = &fl->u.ip6;
        struct sk_buff *skb;
        int err = -ENOMEM;
@@ -463,8 +464,10 @@ static int tcp_v6_send_synack(const struct sock *sk, struct dst_entry *dst,
                        fl6->flowlabel = ip6_flowlabel(ipv6_hdr(ireq->pktopts));
 
                rcu_read_lock();
-               err = ip6_xmit(sk, skb, fl6, rcu_dereference(np->opt),
-                              np->tclass);
+               opt = ireq->ipv6_opt;
+               if (!opt)
+                       opt = rcu_dereference(np->opt);
+               err = ip6_xmit(sk, skb, fl6, opt, np->tclass);
                rcu_read_unlock();
                err = net_xmit_eval(err);
        }
@@ -476,6 +479,7 @@ done:
 
 static void tcp_v6_reqsk_destructor(struct request_sock *req)
 {
+       kfree(inet_rsk(req)->ipv6_opt);
        kfree_skb(inet_rsk(req)->pktopts);
 }
 
@@ -1109,7 +1113,9 @@ static struct sock *tcp_v6_syn_recv_sock(const struct sock *sk, struct sk_buff *
           but we make one more one thing there: reattach optmem
           to newsk.
         */
-       opt = rcu_dereference(np->opt);
+       opt = ireq->ipv6_opt;
+       if (!opt)
+               opt = rcu_dereference(np->opt);
        if (opt) {
                opt = ipv6_dup_options(newsk, opt);
                RCU_INIT_POINTER(newnp->opt, opt);
index fc3598a922b071503514af87deb603a154e2b33b..a0d1e36a284bca11a3cb6762bb7cb26e1ac194e5 100644 (file)
@@ -22,6 +22,7 @@
 #include <linux/skbuff.h>
 #include <linux/init.h>
 #include <linux/poll.h>
+#include <linux/security.h>
 #include <net/sock.h>
 #include <asm/ebcdic.h>
 #include <asm/cpcmd.h>
@@ -530,8 +531,10 @@ static void iucv_sock_close(struct sock *sk)
 
 static void iucv_sock_init(struct sock *sk, struct sock *parent)
 {
-       if (parent)
+       if (parent) {
                sk->sk_type = parent->sk_type;
+               security_sk_clone(parent, sk);
+       }
 }
 
 static struct sock *iucv_sock_alloc(struct socket *sock, int proto, gfp_t prio, int kern)
index 56958c85f2b448576e2aa3daa5e2d6b80569fe8a..d9eaa30ffe3fc89a5d8a3cb9651d02121cc5bf34 100644 (file)
@@ -5,6 +5,7 @@
 config NETLABEL
        bool "NetLabel subsystem support"
        depends on SECURITY
+       select CRC_CCITT if IPV6
        default n
        ---help---
          NetLabel provides support for explicit network packet labeling
index d2732fc952e2adbe224c46cb827fdcd4b38ffbaf..d341ede0dca5f6099c0a1c421c2e3ae9ce112fca 100644 (file)
@@ -12,4 +12,4 @@ obj-y += netlabel_mgmt.o
 # protocol modules
 obj-y  += netlabel_unlabeled.o
 obj-y  += netlabel_cipso_v4.o
-
+obj-$(subst m,y,$(CONFIG_IPV6)) += netlabel_calipso.o
diff --git a/net/netlabel/netlabel_calipso.c b/net/netlabel/netlabel_calipso.c
new file mode 100644 (file)
index 0000000..2ec93c5
--- /dev/null
@@ -0,0 +1,740 @@
+/*
+ * NetLabel CALIPSO/IPv6 Support
+ *
+ * This file defines the CALIPSO/IPv6 functions for the NetLabel system.  The
+ * NetLabel system manages static and dynamic label mappings for network
+ * protocols such as CIPSO and CALIPSO.
+ *
+ * Authors: Paul Moore <paul@paul-moore.com>
+ *          Huw Davies <huw@codeweavers.com>
+ *
+ */
+
+/* (c) Copyright Hewlett-Packard Development Company, L.P., 2006
+ * (c) Copyright Huw Davies <huw@codeweavers.com>, 2015
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <linux/types.h>
+#include <linux/socket.h>
+#include <linux/string.h>
+#include <linux/skbuff.h>
+#include <linux/audit.h>
+#include <linux/slab.h>
+#include <net/sock.h>
+#include <net/netlink.h>
+#include <net/genetlink.h>
+#include <net/netlabel.h>
+#include <net/calipso.h>
+#include <linux/atomic.h>
+
+#include "netlabel_user.h"
+#include "netlabel_calipso.h"
+#include "netlabel_mgmt.h"
+#include "netlabel_domainhash.h"
+
+/* Argument struct for calipso_doi_walk() */
+struct netlbl_calipso_doiwalk_arg {
+       struct netlink_callback *nl_cb;
+       struct sk_buff *skb;
+       u32 seq;
+};
+
+/* Argument struct for netlbl_domhsh_walk() */
+struct netlbl_domhsh_walk_arg {
+       struct netlbl_audit *audit_info;
+       u32 doi;
+};
+
+/* NetLabel Generic NETLINK CALIPSO family */
+static struct genl_family netlbl_calipso_gnl_family = {
+       .id = GENL_ID_GENERATE,
+       .hdrsize = 0,
+       .name = NETLBL_NLTYPE_CALIPSO_NAME,
+       .version = NETLBL_PROTO_VERSION,
+       .maxattr = NLBL_CALIPSO_A_MAX,
+};
+
+/* NetLabel Netlink attribute policy */
+static const struct nla_policy calipso_genl_policy[NLBL_CALIPSO_A_MAX + 1] = {
+       [NLBL_CALIPSO_A_DOI] = { .type = NLA_U32 },
+       [NLBL_CALIPSO_A_MTYPE] = { .type = NLA_U32 },
+};
+
+/* NetLabel Command Handlers
+ */
+/**
+ * netlbl_calipso_add_pass - Adds a CALIPSO pass DOI definition
+ * @info: the Generic NETLINK info block
+ * @audit_info: NetLabel audit information
+ *
+ * Description:
+ * Create a new CALIPSO_MAP_PASS DOI definition based on the given ADD message
+ * and add it to the CALIPSO engine.  Return zero on success and non-zero on
+ * error.
+ *
+ */
+static int netlbl_calipso_add_pass(struct genl_info *info,
+                                  struct netlbl_audit *audit_info)
+{
+       int ret_val;
+       struct calipso_doi *doi_def = NULL;
+
+       doi_def = kmalloc(sizeof(*doi_def), GFP_KERNEL);
+       if (!doi_def)
+               return -ENOMEM;
+       doi_def->type = CALIPSO_MAP_PASS;
+       doi_def->doi = nla_get_u32(info->attrs[NLBL_CALIPSO_A_DOI]);
+       ret_val = calipso_doi_add(doi_def, audit_info);
+       if (ret_val != 0)
+               calipso_doi_free(doi_def);
+
+       return ret_val;
+}
+
+/**
+ * netlbl_calipso_add - Handle an ADD message
+ * @skb: the NETLINK buffer
+ * @info: the Generic NETLINK info block
+ *
+ * Description:
+ * Create a new DOI definition based on the given ADD message and add it to the
+ * CALIPSO engine.  Returns zero on success, negative values on failure.
+ *
+ */
+static int netlbl_calipso_add(struct sk_buff *skb, struct genl_info *info)
+
+{
+       int ret_val = -EINVAL;
+       struct netlbl_audit audit_info;
+
+       if (!info->attrs[NLBL_CALIPSO_A_DOI] ||
+           !info->attrs[NLBL_CALIPSO_A_MTYPE])
+               return -EINVAL;
+
+       netlbl_netlink_auditinfo(skb, &audit_info);
+       switch (nla_get_u32(info->attrs[NLBL_CALIPSO_A_MTYPE])) {
+       case CALIPSO_MAP_PASS:
+               ret_val = netlbl_calipso_add_pass(info, &audit_info);
+               break;
+       }
+       if (ret_val == 0)
+               atomic_inc(&netlabel_mgmt_protocount);
+
+       return ret_val;
+}
+
+/**
+ * netlbl_calipso_list - Handle a LIST message
+ * @skb: the NETLINK buffer
+ * @info: the Generic NETLINK info block
+ *
+ * Description:
+ * Process a user generated LIST message and respond accordingly.
+ * Returns zero on success and negative values on error.
+ *
+ */
+static int netlbl_calipso_list(struct sk_buff *skb, struct genl_info *info)
+{
+       int ret_val;
+       struct sk_buff *ans_skb = NULL;
+       void *data;
+       u32 doi;
+       struct calipso_doi *doi_def;
+
+       if (!info->attrs[NLBL_CALIPSO_A_DOI]) {
+               ret_val = -EINVAL;
+               goto list_failure;
+       }
+
+       doi = nla_get_u32(info->attrs[NLBL_CALIPSO_A_DOI]);
+
+       doi_def = calipso_doi_getdef(doi);
+       if (!doi_def) {
+               ret_val = -EINVAL;
+               goto list_failure;
+       }
+
+       ans_skb = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
+       if (!ans_skb) {
+               ret_val = -ENOMEM;
+               goto list_failure_put;
+       }
+       data = genlmsg_put_reply(ans_skb, info, &netlbl_calipso_gnl_family,
+                                0, NLBL_CALIPSO_C_LIST);
+       if (!data) {
+               ret_val = -ENOMEM;
+               goto list_failure_put;
+       }
+
+       ret_val = nla_put_u32(ans_skb, NLBL_CALIPSO_A_MTYPE, doi_def->type);
+       if (ret_val != 0)
+               goto list_failure_put;
+
+       calipso_doi_putdef(doi_def);
+
+       genlmsg_end(ans_skb, data);
+       return genlmsg_reply(ans_skb, info);
+
+list_failure_put:
+       calipso_doi_putdef(doi_def);
+list_failure:
+       kfree_skb(ans_skb);
+       return ret_val;
+}
+
+/**
+ * netlbl_calipso_listall_cb - calipso_doi_walk() callback for LISTALL
+ * @doi_def: the CALIPSO DOI definition
+ * @arg: the netlbl_calipso_doiwalk_arg structure
+ *
+ * Description:
+ * This function is designed to be used as a callback to the
+ * calipso_doi_walk() function for use in generating a response for a LISTALL
+ * message.  Returns the size of the message on success, negative values on
+ * failure.
+ *
+ */
+static int netlbl_calipso_listall_cb(struct calipso_doi *doi_def, void *arg)
+{
+       int ret_val = -ENOMEM;
+       struct netlbl_calipso_doiwalk_arg *cb_arg = arg;
+       void *data;
+
+       data = genlmsg_put(cb_arg->skb, NETLINK_CB(cb_arg->nl_cb->skb).portid,
+                          cb_arg->seq, &netlbl_calipso_gnl_family,
+                          NLM_F_MULTI, NLBL_CALIPSO_C_LISTALL);
+       if (!data)
+               goto listall_cb_failure;
+
+       ret_val = nla_put_u32(cb_arg->skb, NLBL_CALIPSO_A_DOI, doi_def->doi);
+       if (ret_val != 0)
+               goto listall_cb_failure;
+       ret_val = nla_put_u32(cb_arg->skb,
+                             NLBL_CALIPSO_A_MTYPE,
+                             doi_def->type);
+       if (ret_val != 0)
+               goto listall_cb_failure;
+
+       genlmsg_end(cb_arg->skb, data);
+       return 0;
+
+listall_cb_failure:
+       genlmsg_cancel(cb_arg->skb, data);
+       return ret_val;
+}
+
+/**
+ * netlbl_calipso_listall - Handle a LISTALL message
+ * @skb: the NETLINK buffer
+ * @cb: the NETLINK callback
+ *
+ * Description:
+ * Process a user generated LISTALL message and respond accordingly.  Returns
+ * zero on success and negative values on error.
+ *
+ */
+static int netlbl_calipso_listall(struct sk_buff *skb,
+                                 struct netlink_callback *cb)
+{
+       struct netlbl_calipso_doiwalk_arg cb_arg;
+       u32 doi_skip = cb->args[0];
+
+       cb_arg.nl_cb = cb;
+       cb_arg.skb = skb;
+       cb_arg.seq = cb->nlh->nlmsg_seq;
+
+       calipso_doi_walk(&doi_skip, netlbl_calipso_listall_cb, &cb_arg);
+
+       cb->args[0] = doi_skip;
+       return skb->len;
+}
+
+/**
+ * netlbl_calipso_remove_cb - netlbl_calipso_remove() callback for REMOVE
+ * @entry: LSM domain mapping entry
+ * @arg: the netlbl_domhsh_walk_arg structure
+ *
+ * Description:
+ * This function is intended for use by netlbl_calipso_remove() as the callback
+ * for the netlbl_domhsh_walk() function; it removes LSM domain map entries
+ * which are associated with the CALIPSO DOI specified in @arg.  Returns zero on
+ * success, negative values on failure.
+ *
+ */
+static int netlbl_calipso_remove_cb(struct netlbl_dom_map *entry, void *arg)
+{
+       struct netlbl_domhsh_walk_arg *cb_arg = arg;
+
+       if (entry->def.type == NETLBL_NLTYPE_CALIPSO &&
+           entry->def.calipso->doi == cb_arg->doi)
+               return netlbl_domhsh_remove_entry(entry, cb_arg->audit_info);
+
+       return 0;
+}
+
+/**
+ * netlbl_calipso_remove - Handle a REMOVE message
+ * @skb: the NETLINK buffer
+ * @info: the Generic NETLINK info block
+ *
+ * Description:
+ * Process a user generated REMOVE message and respond accordingly.  Returns
+ * zero on success, negative values on failure.
+ *
+ */
+static int netlbl_calipso_remove(struct sk_buff *skb, struct genl_info *info)
+{
+       int ret_val = -EINVAL;
+       struct netlbl_domhsh_walk_arg cb_arg;
+       struct netlbl_audit audit_info;
+       u32 skip_bkt = 0;
+       u32 skip_chain = 0;
+
+       if (!info->attrs[NLBL_CALIPSO_A_DOI])
+               return -EINVAL;
+
+       netlbl_netlink_auditinfo(skb, &audit_info);
+       cb_arg.doi = nla_get_u32(info->attrs[NLBL_CALIPSO_A_DOI]);
+       cb_arg.audit_info = &audit_info;
+       ret_val = netlbl_domhsh_walk(&skip_bkt, &skip_chain,
+                                    netlbl_calipso_remove_cb, &cb_arg);
+       if (ret_val == 0 || ret_val == -ENOENT) {
+               ret_val = calipso_doi_remove(cb_arg.doi, &audit_info);
+               if (ret_val == 0)
+                       atomic_dec(&netlabel_mgmt_protocount);
+       }
+
+       return ret_val;
+}
+
+/* NetLabel Generic NETLINK Command Definitions
+ */
+
+static const struct genl_ops netlbl_calipso_ops[] = {
+       {
+       .cmd = NLBL_CALIPSO_C_ADD,
+       .flags = GENL_ADMIN_PERM,
+       .policy = calipso_genl_policy,
+       .doit = netlbl_calipso_add,
+       .dumpit = NULL,
+       },
+       {
+       .cmd = NLBL_CALIPSO_C_REMOVE,
+       .flags = GENL_ADMIN_PERM,
+       .policy = calipso_genl_policy,
+       .doit = netlbl_calipso_remove,
+       .dumpit = NULL,
+       },
+       {
+       .cmd = NLBL_CALIPSO_C_LIST,
+       .flags = 0,
+       .policy = calipso_genl_policy,
+       .doit = netlbl_calipso_list,
+       .dumpit = NULL,
+       },
+       {
+       .cmd = NLBL_CALIPSO_C_LISTALL,
+       .flags = 0,
+       .policy = calipso_genl_policy,
+       .doit = NULL,
+       .dumpit = netlbl_calipso_listall,
+       },
+};
+
+/* NetLabel Generic NETLINK Protocol Functions
+ */
+
+/**
+ * netlbl_calipso_genl_init - Register the CALIPSO NetLabel component
+ *
+ * Description:
+ * Register the CALIPSO packet NetLabel component with the Generic NETLINK
+ * mechanism.  Returns zero on success, negative values on failure.
+ *
+ */
+int __init netlbl_calipso_genl_init(void)
+{
+       return genl_register_family_with_ops(&netlbl_calipso_gnl_family,
+                                            netlbl_calipso_ops);
+}
+
+static const struct netlbl_calipso_ops *calipso_ops;
+
+/**
+ * netlbl_calipso_ops_register - Register the CALIPSO operations
+ *
+ * Description:
+ * Register the CALIPSO packet engine operations.
+ *
+ */
+const struct netlbl_calipso_ops *
+netlbl_calipso_ops_register(const struct netlbl_calipso_ops *ops)
+{
+       return xchg(&calipso_ops, ops);
+}
+EXPORT_SYMBOL(netlbl_calipso_ops_register);
+
+static const struct netlbl_calipso_ops *netlbl_calipso_ops_get(void)
+{
+       return ACCESS_ONCE(calipso_ops);
+}
+
+/**
+ * calipso_doi_add - Add a new DOI to the CALIPSO protocol engine
+ * @doi_def: the DOI structure
+ * @audit_info: NetLabel audit information
+ *
+ * Description:
+ * The caller defines a new DOI for use by the CALIPSO engine and calls this
+ * function to add it to the list of acceptable domains.  The caller must
+ * ensure that the mapping table specified in @doi_def->map meets all of the
+ * requirements of the mapping type (see calipso.h for details).  Returns
+ * zero on success and non-zero on failure.
+ *
+ */
+int calipso_doi_add(struct calipso_doi *doi_def,
+                   struct netlbl_audit *audit_info)
+{
+       int ret_val = -ENOMSG;
+       const struct netlbl_calipso_ops *ops = netlbl_calipso_ops_get();
+
+       if (ops)
+               ret_val = ops->doi_add(doi_def, audit_info);
+       return ret_val;
+}
+
+/**
+ * calipso_doi_free - Frees a DOI definition
+ * @doi_def: the DOI definition
+ *
+ * Description:
+ * This function frees all of the memory associated with a DOI definition.
+ *
+ */
+void calipso_doi_free(struct calipso_doi *doi_def)
+{
+       const struct netlbl_calipso_ops *ops = netlbl_calipso_ops_get();
+
+       if (ops)
+               ops->doi_free(doi_def);
+}
+
+/**
+ * calipso_doi_remove - Remove an existing DOI from the CALIPSO protocol engine
+ * @doi: the DOI value
+ * @audit_secid: the LSM secid to use in the audit message
+ *
+ * Description:
+ * Removes a DOI definition from the CALIPSO engine.  The NetLabel routines will
+ * be called to release their own LSM domain mappings as well as our own
+ * domain list.  Returns zero on success and negative values on failure.
+ *
+ */
+int calipso_doi_remove(u32 doi, struct netlbl_audit *audit_info)
+{
+       int ret_val = -ENOMSG;
+       const struct netlbl_calipso_ops *ops = netlbl_calipso_ops_get();
+
+       if (ops)
+               ret_val = ops->doi_remove(doi, audit_info);
+       return ret_val;
+}
+
+/**
+ * calipso_doi_getdef - Returns a reference to a valid DOI definition
+ * @doi: the DOI value
+ *
+ * Description:
+ * Searches for a valid DOI definition and if one is found it is returned to
+ * the caller.  Otherwise NULL is returned.  The caller must ensure that
+ * calipso_doi_putdef() is called when the caller is done.
+ *
+ */
+struct calipso_doi *calipso_doi_getdef(u32 doi)
+{
+       struct calipso_doi *ret_val = NULL;
+       const struct netlbl_calipso_ops *ops = netlbl_calipso_ops_get();
+
+       if (ops)
+               ret_val = ops->doi_getdef(doi);
+       return ret_val;
+}
+
+/**
+ * calipso_doi_putdef - Releases a reference for the given DOI definition
+ * @doi_def: the DOI definition
+ *
+ * Description:
+ * Releases a DOI definition reference obtained from calipso_doi_getdef().
+ *
+ */
+void calipso_doi_putdef(struct calipso_doi *doi_def)
+{
+       const struct netlbl_calipso_ops *ops = netlbl_calipso_ops_get();
+
+       if (ops)
+               ops->doi_putdef(doi_def);
+}
+
+/**
+ * calipso_doi_walk - Iterate through the DOI definitions
+ * @skip_cnt: skip past this number of DOI definitions, updated
+ * @callback: callback for each DOI definition
+ * @cb_arg: argument for the callback function
+ *
+ * Description:
+ * Iterate over the DOI definition list, skipping the first @skip_cnt entries.
+ * For each entry call @callback, if @callback returns a negative value stop
+ * 'walking' through the list and return.  Updates the value in @skip_cnt upon
+ * return.  Returns zero on success, negative values on failure.
+ *
+ */
+int calipso_doi_walk(u32 *skip_cnt,
+                    int (*callback)(struct calipso_doi *doi_def, void *arg),
+                    void *cb_arg)
+{
+       int ret_val = -ENOMSG;
+       const struct netlbl_calipso_ops *ops = netlbl_calipso_ops_get();
+
+       if (ops)
+               ret_val = ops->doi_walk(skip_cnt, callback, cb_arg);
+       return ret_val;
+}
+
+/**
+ * calipso_sock_getattr - Get the security attributes from a sock
+ * @sk: the sock
+ * @secattr: the security attributes
+ *
+ * Description:
+ * Query @sk to see if there is a CALIPSO option attached to the sock and if
+ * there is return the CALIPSO security attributes in @secattr.  This function
+ * requires that @sk be locked, or privately held, but it does not do any
+ * locking itself.  Returns zero on success and negative values on failure.
+ *
+ */
+int calipso_sock_getattr(struct sock *sk, struct netlbl_lsm_secattr *secattr)
+{
+       int ret_val = -ENOMSG;
+       const struct netlbl_calipso_ops *ops = netlbl_calipso_ops_get();
+
+       if (ops)
+               ret_val = ops->sock_getattr(sk, secattr);
+       return ret_val;
+}
+
+/**
+ * calipso_sock_setattr - Add a CALIPSO option to a socket
+ * @sk: the socket
+ * @doi_def: the CALIPSO DOI to use
+ * @secattr: the specific security attributes of the socket
+ *
+ * Description:
+ * Set the CALIPSO option on the given socket using the DOI definition and
+ * security attributes passed to the function.  This function requires
+ * exclusive access to @sk, which means it either needs to be in the
+ * process of being created or locked.  Returns zero on success and negative
+ * values on failure.
+ *
+ */
+int calipso_sock_setattr(struct sock *sk,
+                        const struct calipso_doi *doi_def,
+                        const struct netlbl_lsm_secattr *secattr)
+{
+       int ret_val = -ENOMSG;
+       const struct netlbl_calipso_ops *ops = netlbl_calipso_ops_get();
+
+       if (ops)
+               ret_val = ops->sock_setattr(sk, doi_def, secattr);
+       return ret_val;
+}
+
+/**
+ * calipso_sock_delattr - Delete the CALIPSO option from a socket
+ * @sk: the socket
+ *
+ * Description:
+ * Removes the CALIPSO option from a socket, if present.
+ *
+ */
+void calipso_sock_delattr(struct sock *sk)
+{
+       const struct netlbl_calipso_ops *ops = netlbl_calipso_ops_get();
+
+       if (ops)
+               ops->sock_delattr(sk);
+}
+
+/**
+ * calipso_req_setattr - Add a CALIPSO option to a connection request socket
+ * @req: the connection request socket
+ * @doi_def: the CALIPSO DOI to use
+ * @secattr: the specific security attributes of the socket
+ *
+ * Description:
+ * Set the CALIPSO option on the given socket using the DOI definition and
+ * security attributes passed to the function.  Returns zero on success and
+ * negative values on failure.
+ *
+ */
+int calipso_req_setattr(struct request_sock *req,
+                       const struct calipso_doi *doi_def,
+                       const struct netlbl_lsm_secattr *secattr)
+{
+       int ret_val = -ENOMSG;
+       const struct netlbl_calipso_ops *ops = netlbl_calipso_ops_get();
+
+       if (ops)
+               ret_val = ops->req_setattr(req, doi_def, secattr);
+       return ret_val;
+}
+
+/**
+ * calipso_req_delattr - Delete the CALIPSO option from a request socket
+ * @reg: the request socket
+ *
+ * Description:
+ * Removes the CALIPSO option from a request socket, if present.
+ *
+ */
+void calipso_req_delattr(struct request_sock *req)
+{
+       const struct netlbl_calipso_ops *ops = netlbl_calipso_ops_get();
+
+       if (ops)
+               ops->req_delattr(req);
+}
+
+/**
+ * calipso_optptr - Find the CALIPSO option in the packet
+ * @skb: the packet
+ *
+ * Description:
+ * Parse the packet's IP header looking for a CALIPSO option.  Returns a pointer
+ * to the start of the CALIPSO option on success, NULL if one if not found.
+ *
+ */
+unsigned char *calipso_optptr(const struct sk_buff *skb)
+{
+       unsigned char *ret_val = NULL;
+       const struct netlbl_calipso_ops *ops = netlbl_calipso_ops_get();
+
+       if (ops)
+               ret_val = ops->skbuff_optptr(skb);
+       return ret_val;
+}
+
+/**
+ * calipso_getattr - Get the security attributes from a memory block.
+ * @calipso: the CALIPSO option
+ * @secattr: the security attributes
+ *
+ * Description:
+ * Inspect @calipso and return the security attributes in @secattr.
+ * Returns zero on success and negative values on failure.
+ *
+ */
+int calipso_getattr(const unsigned char *calipso,
+                   struct netlbl_lsm_secattr *secattr)
+{
+       int ret_val = -ENOMSG;
+       const struct netlbl_calipso_ops *ops = netlbl_calipso_ops_get();
+
+       if (ops)
+               ret_val = ops->opt_getattr(calipso, secattr);
+       return ret_val;
+}
+
+/**
+ * calipso_skbuff_setattr - Set the CALIPSO option on a packet
+ * @skb: the packet
+ * @doi_def: the CALIPSO DOI to use
+ * @secattr: the security attributes
+ *
+ * Description:
+ * Set the CALIPSO option on the given packet based on the security attributes.
+ * Returns a pointer to the IP header on success and NULL on failure.
+ *
+ */
+int calipso_skbuff_setattr(struct sk_buff *skb,
+                          const struct calipso_doi *doi_def,
+                          const struct netlbl_lsm_secattr *secattr)
+{
+       int ret_val = -ENOMSG;
+       const struct netlbl_calipso_ops *ops = netlbl_calipso_ops_get();
+
+       if (ops)
+               ret_val = ops->skbuff_setattr(skb, doi_def, secattr);
+       return ret_val;
+}
+
+/**
+ * calipso_skbuff_delattr - Delete any CALIPSO options from a packet
+ * @skb: the packet
+ *
+ * Description:
+ * Removes any and all CALIPSO options from the given packet.  Returns zero on
+ * success, negative values on failure.
+ *
+ */
+int calipso_skbuff_delattr(struct sk_buff *skb)
+{
+       int ret_val = -ENOMSG;
+       const struct netlbl_calipso_ops *ops = netlbl_calipso_ops_get();
+
+       if (ops)
+               ret_val = ops->skbuff_delattr(skb);
+       return ret_val;
+}
+
+/**
+ * calipso_cache_invalidate - Invalidates the current CALIPSO cache
+ *
+ * Description:
+ * Invalidates and frees any entries in the CALIPSO cache.  Returns zero on
+ * success and negative values on failure.
+ *
+ */
+void calipso_cache_invalidate(void)
+{
+       const struct netlbl_calipso_ops *ops = netlbl_calipso_ops_get();
+
+       if (ops)
+               ops->cache_invalidate();
+}
+
+/**
+ * calipso_cache_add - Add an entry to the CALIPSO cache
+ * @calipso_ptr: the CALIPSO option
+ * @secattr: the packet's security attributes
+ *
+ * Description:
+ * Add a new entry into the CALIPSO label mapping cache.
+ * Returns zero on success, negative values on failure.
+ *
+ */
+int calipso_cache_add(const unsigned char *calipso_ptr,
+                     const struct netlbl_lsm_secattr *secattr)
+
+{
+       int ret_val = -ENOMSG;
+       const struct netlbl_calipso_ops *ops = netlbl_calipso_ops_get();
+
+       if (ops)
+               ret_val = ops->cache_add(calipso_ptr, secattr);
+       return ret_val;
+}
diff --git a/net/netlabel/netlabel_calipso.h b/net/netlabel/netlabel_calipso.h
new file mode 100644 (file)
index 0000000..9fd291c
--- /dev/null
@@ -0,0 +1,151 @@
+/*
+ * NetLabel CALIPSO Support
+ *
+ * This file defines the CALIPSO functions for the NetLabel system.  The
+ * NetLabel system manages static and dynamic label mappings for network
+ * protocols such as CIPSO and RIPSO.
+ *
+ * Authors: Paul Moore <paul@paul-moore.com>
+ *          Huw Davies <huw@codeweavers.com>
+ *
+ */
+
+/* (c) Copyright Hewlett-Packard Development Company, L.P., 2006
+ * (c) Copyright Huw Davies <huw@codeweavers.com>, 2015
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef _NETLABEL_CALIPSO
+#define _NETLABEL_CALIPSO
+
+#include <net/netlabel.h>
+#include <net/calipso.h>
+
+/* The following NetLabel payloads are supported by the CALIPSO subsystem.
+ *
+ * o ADD:
+ *   Sent by an application to add a new DOI mapping table.
+ *
+ *   Required attributes:
+ *
+ *     NLBL_CALIPSO_A_DOI
+ *     NLBL_CALIPSO_A_MTYPE
+ *
+ *   If using CALIPSO_MAP_PASS no additional attributes are required.
+ *
+ * o REMOVE:
+ *   Sent by an application to remove a specific DOI mapping table from the
+ *   CALIPSO system.
+ *
+ *   Required attributes:
+ *
+ *     NLBL_CALIPSO_A_DOI
+ *
+ * o LIST:
+ *   Sent by an application to list the details of a DOI definition.  On
+ *   success the kernel should send a response using the following format.
+ *
+ *   Required attributes:
+ *
+ *     NLBL_CALIPSO_A_DOI
+ *
+ *   The valid response message format depends on the type of the DOI mapping,
+ *   the defined formats are shown below.
+ *
+ *   Required attributes:
+ *
+ *     NLBL_CALIPSO_A_MTYPE
+ *
+ *   If using CALIPSO_MAP_PASS no additional attributes are required.
+ *
+ * o LISTALL:
+ *   This message is sent by an application to list the valid DOIs on the
+ *   system.  When sent by an application there is no payload and the
+ *   NLM_F_DUMP flag should be set.  The kernel should respond with a series of
+ *   the following messages.
+ *
+ *   Required attributes:
+ *
+ *    NLBL_CALIPSO_A_DOI
+ *    NLBL_CALIPSO_A_MTYPE
+ *
+ */
+
+/* NetLabel CALIPSO commands */
+enum {
+       NLBL_CALIPSO_C_UNSPEC,
+       NLBL_CALIPSO_C_ADD,
+       NLBL_CALIPSO_C_REMOVE,
+       NLBL_CALIPSO_C_LIST,
+       NLBL_CALIPSO_C_LISTALL,
+       __NLBL_CALIPSO_C_MAX,
+};
+
+/* NetLabel CALIPSO attributes */
+enum {
+       NLBL_CALIPSO_A_UNSPEC,
+       NLBL_CALIPSO_A_DOI,
+       /* (NLA_U32)
+        * the DOI value */
+       NLBL_CALIPSO_A_MTYPE,
+       /* (NLA_U32)
+        * the mapping table type (defined in the calipso.h header as
+        * CALIPSO_MAP_*) */
+       __NLBL_CALIPSO_A_MAX,
+};
+
+#define NLBL_CALIPSO_A_MAX (__NLBL_CALIPSO_A_MAX - 1)
+
+/* NetLabel protocol functions */
+#if IS_ENABLED(CONFIG_IPV6)
+int netlbl_calipso_genl_init(void);
+#else
+static inline int netlbl_calipso_genl_init(void)
+{
+       return 0;
+}
+#endif
+
+int calipso_doi_add(struct calipso_doi *doi_def,
+                   struct netlbl_audit *audit_info);
+void calipso_doi_free(struct calipso_doi *doi_def);
+int calipso_doi_remove(u32 doi, struct netlbl_audit *audit_info);
+struct calipso_doi *calipso_doi_getdef(u32 doi);
+void calipso_doi_putdef(struct calipso_doi *doi_def);
+int calipso_doi_walk(u32 *skip_cnt,
+                    int (*callback)(struct calipso_doi *doi_def, void *arg),
+                    void *cb_arg);
+int calipso_sock_getattr(struct sock *sk, struct netlbl_lsm_secattr *secattr);
+int calipso_sock_setattr(struct sock *sk,
+                        const struct calipso_doi *doi_def,
+                        const struct netlbl_lsm_secattr *secattr);
+void calipso_sock_delattr(struct sock *sk);
+int calipso_req_setattr(struct request_sock *req,
+                       const struct calipso_doi *doi_def,
+                       const struct netlbl_lsm_secattr *secattr);
+void calipso_req_delattr(struct request_sock *req);
+unsigned char *calipso_optptr(const struct sk_buff *skb);
+int calipso_getattr(const unsigned char *calipso,
+                   struct netlbl_lsm_secattr *secattr);
+int calipso_skbuff_setattr(struct sk_buff *skb,
+                          const struct calipso_doi *doi_def,
+                          const struct netlbl_lsm_secattr *secattr);
+int calipso_skbuff_delattr(struct sk_buff *skb);
+void calipso_cache_invalidate(void);
+int calipso_cache_add(const unsigned char *calipso_ptr,
+                     const struct netlbl_lsm_secattr *secattr);
+
+#endif
index ada67422234bc41ba73f23696607903d800d87e3..41d0e95d171e185828054733f8a26d099da65cff 100644 (file)
 #include <linux/slab.h>
 #include <net/netlabel.h>
 #include <net/cipso_ipv4.h>
+#include <net/calipso.h>
 #include <asm/bug.h>
 
 #include "netlabel_mgmt.h"
 #include "netlabel_addrlist.h"
+#include "netlabel_calipso.h"
 #include "netlabel_domainhash.h"
 #include "netlabel_user.h"
 
@@ -55,8 +57,9 @@ struct netlbl_domhsh_tbl {
 static DEFINE_SPINLOCK(netlbl_domhsh_lock);
 #define netlbl_domhsh_rcu_deref(p) \
        rcu_dereference_check(p, lockdep_is_held(&netlbl_domhsh_lock))
-static struct netlbl_domhsh_tbl *netlbl_domhsh;
-static struct netlbl_dom_map *netlbl_domhsh_def;
+static struct netlbl_domhsh_tbl __rcu *netlbl_domhsh;
+static struct netlbl_dom_map __rcu *netlbl_domhsh_def_ipv4;
+static struct netlbl_dom_map __rcu *netlbl_domhsh_def_ipv6;
 
 /*
  * Domain Hash Table Helper Functions
@@ -126,18 +129,26 @@ static u32 netlbl_domhsh_hash(const char *key)
        return val & (netlbl_domhsh_rcu_deref(netlbl_domhsh)->size - 1);
 }
 
+static bool netlbl_family_match(u16 f1, u16 f2)
+{
+       return (f1 == f2) || (f1 == AF_UNSPEC) || (f2 == AF_UNSPEC);
+}
+
 /**
  * netlbl_domhsh_search - Search for a domain entry
  * @domain: the domain
+ * @family: the address family
  *
  * Description:
  * Searches the domain hash table and returns a pointer to the hash table
- * entry if found, otherwise NULL is returned.  The caller is responsible for
+ * entry if found, otherwise NULL is returned.  @family may be %AF_UNSPEC
+ * which matches any address family entries.  The caller is responsible for
  * ensuring that the hash table is protected with either a RCU read lock or the
  * hash table lock.
  *
  */
-static struct netlbl_dom_map *netlbl_domhsh_search(const char *domain)
+static struct netlbl_dom_map *netlbl_domhsh_search(const char *domain,
+                                                  u16 family)
 {
        u32 bkt;
        struct list_head *bkt_list;
@@ -147,7 +158,9 @@ static struct netlbl_dom_map *netlbl_domhsh_search(const char *domain)
                bkt = netlbl_domhsh_hash(domain);
                bkt_list = &netlbl_domhsh_rcu_deref(netlbl_domhsh)->tbl[bkt];
                list_for_each_entry_rcu(iter, bkt_list, list)
-                       if (iter->valid && strcmp(iter->domain, domain) == 0)
+                       if (iter->valid &&
+                           netlbl_family_match(iter->family, family) &&
+                           strcmp(iter->domain, domain) == 0)
                                return iter;
        }
 
@@ -157,28 +170,37 @@ static struct netlbl_dom_map *netlbl_domhsh_search(const char *domain)
 /**
  * netlbl_domhsh_search_def - Search for a domain entry
  * @domain: the domain
- * @def: return default if no match is found
+ * @family: the address family
  *
  * Description:
  * Searches the domain hash table and returns a pointer to the hash table
  * entry if an exact match is found, if an exact match is not present in the
  * hash table then the default entry is returned if valid otherwise NULL is
- * returned.  The caller is responsible ensuring that the hash table is
+ * returned.  @family may be %AF_UNSPEC which matches any address family
+ * entries.  The caller is responsible ensuring that the hash table is
  * protected with either a RCU read lock or the hash table lock.
  *
  */
-static struct netlbl_dom_map *netlbl_domhsh_search_def(const char *domain)
+static struct netlbl_dom_map *netlbl_domhsh_search_def(const char *domain,
+                                                      u16 family)
 {
        struct netlbl_dom_map *entry;
 
-       entry = netlbl_domhsh_search(domain);
-       if (entry == NULL) {
-               entry = netlbl_domhsh_rcu_deref(netlbl_domhsh_def);
-               if (entry != NULL && !entry->valid)
-                       entry = NULL;
+       entry = netlbl_domhsh_search(domain, family);
+       if (entry != NULL)
+               return entry;
+       if (family == AF_INET || family == AF_UNSPEC) {
+               entry = netlbl_domhsh_rcu_deref(netlbl_domhsh_def_ipv4);
+               if (entry != NULL && entry->valid)
+                       return entry;
+       }
+       if (family == AF_INET6 || family == AF_UNSPEC) {
+               entry = netlbl_domhsh_rcu_deref(netlbl_domhsh_def_ipv6);
+               if (entry != NULL && entry->valid)
+                       return entry;
        }
 
-       return entry;
+       return NULL;
 }
 
 /**
@@ -203,6 +225,7 @@ static void netlbl_domhsh_audit_add(struct netlbl_dom_map *entry,
 {
        struct audit_buffer *audit_buf;
        struct cipso_v4_doi *cipsov4 = NULL;
+       struct calipso_doi *calipso = NULL;
        u32 type;
 
        audit_buf = netlbl_audit_start_common(AUDIT_MAC_MAP_ADD, audit_info);
@@ -221,12 +244,14 @@ static void netlbl_domhsh_audit_add(struct netlbl_dom_map *entry,
                        struct netlbl_domaddr6_map *map6;
                        map6 = netlbl_domhsh_addr6_entry(addr6);
                        type = map6->def.type;
+                       calipso = map6->def.calipso;
                        netlbl_af6list_audit_addr(audit_buf, 0, NULL,
                                                  &addr6->addr, &addr6->mask);
 #endif /* IPv6 */
                } else {
                        type = entry->def.type;
                        cipsov4 = entry->def.cipso;
+                       calipso = entry->def.calipso;
                }
                switch (type) {
                case NETLBL_NLTYPE_UNLABELED:
@@ -238,6 +263,12 @@ static void netlbl_domhsh_audit_add(struct netlbl_dom_map *entry,
                                         " nlbl_protocol=cipsov4 cipso_doi=%u",
                                         cipsov4->doi);
                        break;
+               case NETLBL_NLTYPE_CALIPSO:
+                       BUG_ON(calipso == NULL);
+                       audit_log_format(audit_buf,
+                                        " nlbl_protocol=calipso calipso_doi=%u",
+                                        calipso->doi);
+                       break;
                }
                audit_log_format(audit_buf, " res=%u", result == 0 ? 1 : 0);
                audit_log_end(audit_buf);
@@ -264,13 +295,25 @@ static int netlbl_domhsh_validate(const struct netlbl_dom_map *entry)
        if (entry == NULL)
                return -EINVAL;
 
+       if (entry->family != AF_INET && entry->family != AF_INET6 &&
+           (entry->family != AF_UNSPEC ||
+            entry->def.type != NETLBL_NLTYPE_UNLABELED))
+               return -EINVAL;
+
        switch (entry->def.type) {
        case NETLBL_NLTYPE_UNLABELED:
-               if (entry->def.cipso != NULL || entry->def.addrsel != NULL)
+               if (entry->def.cipso != NULL || entry->def.calipso != NULL ||
+                   entry->def.addrsel != NULL)
                        return -EINVAL;
                break;
        case NETLBL_NLTYPE_CIPSOV4:
-               if (entry->def.cipso == NULL)
+               if (entry->family != AF_INET ||
+                   entry->def.cipso == NULL)
+                       return -EINVAL;
+               break;
+       case NETLBL_NLTYPE_CALIPSO:
+               if (entry->family != AF_INET6 ||
+                   entry->def.calipso == NULL)
                        return -EINVAL;
                break;
        case NETLBL_NLTYPE_ADDRSELECT:
@@ -294,6 +337,12 @@ static int netlbl_domhsh_validate(const struct netlbl_dom_map *entry)
                        map6 = netlbl_domhsh_addr6_entry(iter6);
                        switch (map6->def.type) {
                        case NETLBL_NLTYPE_UNLABELED:
+                               if (map6->def.calipso != NULL)
+                                       return -EINVAL;
+                               break;
+                       case NETLBL_NLTYPE_CALIPSO:
+                               if (map6->def.calipso == NULL)
+                                       return -EINVAL;
                                break;
                        default:
                                return -EINVAL;
@@ -358,15 +407,18 @@ int __init netlbl_domhsh_init(u32 size)
  *
  * Description:
  * Adds a new entry to the domain hash table and handles any updates to the
- * lower level protocol handler (i.e. CIPSO).  Returns zero on success,
- * negative on failure.
+ * lower level protocol handler (i.e. CIPSO).  @entry->family may be set to
+ * %AF_UNSPEC which will add an entry that matches all address families.  This
+ * is only useful for the unlabelled type and will only succeed if there is no
+ * existing entry for any address family with the same domain.  Returns zero
+ * on success, negative on failure.
  *
  */
 int netlbl_domhsh_add(struct netlbl_dom_map *entry,
                      struct netlbl_audit *audit_info)
 {
        int ret_val = 0;
-       struct netlbl_dom_map *entry_old;
+       struct netlbl_dom_map *entry_old, *entry_b;
        struct netlbl_af4list *iter4;
        struct netlbl_af4list *tmp4;
 #if IS_ENABLED(CONFIG_IPV6)
@@ -385,9 +437,10 @@ int netlbl_domhsh_add(struct netlbl_dom_map *entry,
        rcu_read_lock();
        spin_lock(&netlbl_domhsh_lock);
        if (entry->domain != NULL)
-               entry_old = netlbl_domhsh_search(entry->domain);
+               entry_old = netlbl_domhsh_search(entry->domain, entry->family);
        else
-               entry_old = netlbl_domhsh_search_def(entry->domain);
+               entry_old = netlbl_domhsh_search_def(entry->domain,
+                                                    entry->family);
        if (entry_old == NULL) {
                entry->valid = 1;
 
@@ -397,7 +450,41 @@ int netlbl_domhsh_add(struct netlbl_dom_map *entry,
                                    &rcu_dereference(netlbl_domhsh)->tbl[bkt]);
                } else {
                        INIT_LIST_HEAD(&entry->list);
-                       rcu_assign_pointer(netlbl_domhsh_def, entry);
+                       switch (entry->family) {
+                       case AF_INET:
+                               rcu_assign_pointer(netlbl_domhsh_def_ipv4,
+                                                  entry);
+                               break;
+                       case AF_INET6:
+                               rcu_assign_pointer(netlbl_domhsh_def_ipv6,
+                                                  entry);
+                               break;
+                       case AF_UNSPEC:
+                               if (entry->def.type !=
+                                   NETLBL_NLTYPE_UNLABELED) {
+                                       ret_val = -EINVAL;
+                                       goto add_return;
+                               }
+                               entry_b = kzalloc(sizeof(*entry_b), GFP_ATOMIC);
+                               if (entry_b == NULL) {
+                                       ret_val = -ENOMEM;
+                                       goto add_return;
+                               }
+                               entry_b->family = AF_INET6;
+                               entry_b->def.type = NETLBL_NLTYPE_UNLABELED;
+                               entry_b->valid = 1;
+                               entry->family = AF_INET;
+                               rcu_assign_pointer(netlbl_domhsh_def_ipv4,
+                                                  entry);
+                               rcu_assign_pointer(netlbl_domhsh_def_ipv6,
+                                                  entry_b);
+                               break;
+                       default:
+                               /* Already checked in
+                                * netlbl_domhsh_validate(). */
+                               ret_val = -EINVAL;
+                               goto add_return;
+                       }
                }
 
                if (entry->def.type == NETLBL_NLTYPE_ADDRSELECT) {
@@ -513,10 +600,12 @@ int netlbl_domhsh_remove_entry(struct netlbl_dom_map *entry,
        spin_lock(&netlbl_domhsh_lock);
        if (entry->valid) {
                entry->valid = 0;
-               if (entry != rcu_dereference(netlbl_domhsh_def))
-                       list_del_rcu(&entry->list);
+               if (entry == rcu_dereference(netlbl_domhsh_def_ipv4))
+                       RCU_INIT_POINTER(netlbl_domhsh_def_ipv4, NULL);
+               else if (entry == rcu_dereference(netlbl_domhsh_def_ipv6))
+                       RCU_INIT_POINTER(netlbl_domhsh_def_ipv6, NULL);
                else
-                       RCU_INIT_POINTER(netlbl_domhsh_def, NULL);
+                       list_del_rcu(&entry->list);
        } else
                ret_val = -ENOENT;
        spin_unlock(&netlbl_domhsh_lock);
@@ -533,6 +622,10 @@ int netlbl_domhsh_remove_entry(struct netlbl_dom_map *entry,
        if (ret_val == 0) {
                struct netlbl_af4list *iter4;
                struct netlbl_domaddr4_map *map4;
+#if IS_ENABLED(CONFIG_IPV6)
+               struct netlbl_af6list *iter6;
+               struct netlbl_domaddr6_map *map6;
+#endif /* IPv6 */
 
                switch (entry->def.type) {
                case NETLBL_NLTYPE_ADDRSELECT:
@@ -541,12 +634,22 @@ int netlbl_domhsh_remove_entry(struct netlbl_dom_map *entry,
                                map4 = netlbl_domhsh_addr4_entry(iter4);
                                cipso_v4_doi_putdef(map4->def.cipso);
                        }
-                       /* no need to check the IPv6 list since we currently
-                        * support only unlabeled protocols for IPv6 */
+#if IS_ENABLED(CONFIG_IPV6)
+                       netlbl_af6list_foreach_rcu(iter6,
+                                            &entry->def.addrsel->list6) {
+                               map6 = netlbl_domhsh_addr6_entry(iter6);
+                               calipso_doi_putdef(map6->def.calipso);
+                       }
+#endif /* IPv6 */
                        break;
                case NETLBL_NLTYPE_CIPSOV4:
                        cipso_v4_doi_putdef(entry->def.cipso);
                        break;
+#if IS_ENABLED(CONFIG_IPV6)
+               case NETLBL_NLTYPE_CALIPSO:
+                       calipso_doi_putdef(entry->def.calipso);
+                       break;
+#endif /* IPv6 */
                }
                call_rcu(&entry->rcu, netlbl_domhsh_free_entry);
        }
@@ -583,9 +686,9 @@ int netlbl_domhsh_remove_af4(const char *domain,
        rcu_read_lock();
 
        if (domain)
-               entry_map = netlbl_domhsh_search(domain);
+               entry_map = netlbl_domhsh_search(domain, AF_INET);
        else
-               entry_map = netlbl_domhsh_search_def(domain);
+               entry_map = netlbl_domhsh_search_def(domain, AF_INET);
        if (entry_map == NULL ||
            entry_map->def.type != NETLBL_NLTYPE_ADDRSELECT)
                goto remove_af4_failure;
@@ -622,28 +725,114 @@ remove_af4_failure:
        return -ENOENT;
 }
 
+#if IS_ENABLED(CONFIG_IPV6)
+/**
+ * netlbl_domhsh_remove_af6 - Removes an address selector entry
+ * @domain: the domain
+ * @addr: IPv6 address
+ * @mask: IPv6 address mask
+ * @audit_info: NetLabel audit information
+ *
+ * Description:
+ * Removes an individual address selector from a domain mapping and potentially
+ * the entire mapping if it is empty.  Returns zero on success, negative values
+ * on failure.
+ *
+ */
+int netlbl_domhsh_remove_af6(const char *domain,
+                            const struct in6_addr *addr,
+                            const struct in6_addr *mask,
+                            struct netlbl_audit *audit_info)
+{
+       struct netlbl_dom_map *entry_map;
+       struct netlbl_af6list *entry_addr;
+       struct netlbl_af4list *iter4;
+       struct netlbl_af6list *iter6;
+       struct netlbl_domaddr6_map *entry;
+
+       rcu_read_lock();
+
+       if (domain)
+               entry_map = netlbl_domhsh_search(domain, AF_INET6);
+       else
+               entry_map = netlbl_domhsh_search_def(domain, AF_INET6);
+       if (entry_map == NULL ||
+           entry_map->def.type != NETLBL_NLTYPE_ADDRSELECT)
+               goto remove_af6_failure;
+
+       spin_lock(&netlbl_domhsh_lock);
+       entry_addr = netlbl_af6list_remove(addr, mask,
+                                          &entry_map->def.addrsel->list6);
+       spin_unlock(&netlbl_domhsh_lock);
+
+       if (entry_addr == NULL)
+               goto remove_af6_failure;
+       netlbl_af4list_foreach_rcu(iter4, &entry_map->def.addrsel->list4)
+               goto remove_af6_single_addr;
+       netlbl_af6list_foreach_rcu(iter6, &entry_map->def.addrsel->list6)
+               goto remove_af6_single_addr;
+       /* the domain mapping is empty so remove it from the mapping table */
+       netlbl_domhsh_remove_entry(entry_map, audit_info);
+
+remove_af6_single_addr:
+       rcu_read_unlock();
+       /* yick, we can't use call_rcu here because we don't have a rcu head
+        * pointer but hopefully this should be a rare case so the pause
+        * shouldn't be a problem */
+       synchronize_rcu();
+       entry = netlbl_domhsh_addr6_entry(entry_addr);
+       calipso_doi_putdef(entry->def.calipso);
+       kfree(entry);
+       return 0;
+
+remove_af6_failure:
+       rcu_read_unlock();
+       return -ENOENT;
+}
+#endif /* IPv6 */
+
 /**
  * netlbl_domhsh_remove - Removes an entry from the domain hash table
  * @domain: the domain to remove
+ * @family: address family
  * @audit_info: NetLabel audit information
  *
  * Description:
  * Removes an entry from the domain hash table and handles any updates to the
- * lower level protocol handler (i.e. CIPSO).  Returns zero on success,
- * negative on failure.
+ * lower level protocol handler (i.e. CIPSO).  @family may be %AF_UNSPEC which
+ * removes all address family entries.  Returns zero on success, negative on
+ * failure.
  *
  */
-int netlbl_domhsh_remove(const char *domain, struct netlbl_audit *audit_info)
+int netlbl_domhsh_remove(const char *domain, u16 family,
+                        struct netlbl_audit *audit_info)
 {
-       int ret_val;
+       int ret_val = -EINVAL;
        struct netlbl_dom_map *entry;
 
        rcu_read_lock();
-       if (domain)
-               entry = netlbl_domhsh_search(domain);
-       else
-               entry = netlbl_domhsh_search_def(domain);
-       ret_val = netlbl_domhsh_remove_entry(entry, audit_info);
+
+       if (family == AF_INET || family == AF_UNSPEC) {
+               if (domain)
+                       entry = netlbl_domhsh_search(domain, AF_INET);
+               else
+                       entry = netlbl_domhsh_search_def(domain, AF_INET);
+               ret_val = netlbl_domhsh_remove_entry(entry, audit_info);
+               if (ret_val && ret_val != -ENOENT)
+                       goto done;
+       }
+       if (family == AF_INET6 || family == AF_UNSPEC) {
+               int ret_val2;
+
+               if (domain)
+                       entry = netlbl_domhsh_search(domain, AF_INET6);
+               else
+                       entry = netlbl_domhsh_search_def(domain, AF_INET6);
+               ret_val2 = netlbl_domhsh_remove_entry(entry, audit_info);
+               if (ret_val2 != -ENOENT)
+                       ret_val = ret_val2;
+       }
+done:
        rcu_read_unlock();
 
        return ret_val;
@@ -651,32 +840,38 @@ int netlbl_domhsh_remove(const char *domain, struct netlbl_audit *audit_info)
 
 /**
  * netlbl_domhsh_remove_default - Removes the default entry from the table
+ * @family: address family
  * @audit_info: NetLabel audit information
  *
  * Description:
- * Removes/resets the default entry for the domain hash table and handles any
- * updates to the lower level protocol handler (i.e. CIPSO).  Returns zero on
- * success, non-zero on failure.
+ * Removes/resets the default entry corresponding to @family from the domain
+ * hash table and handles any updates to the lower level protocol handler
+ * (i.e. CIPSO).  @family may be %AF_UNSPEC which removes all address family
+ * entries.  Returns zero on success, negative on failure.
  *
  */
-int netlbl_domhsh_remove_default(struct netlbl_audit *audit_info)
+int netlbl_domhsh_remove_default(u16 family, struct netlbl_audit *audit_info)
 {
-       return netlbl_domhsh_remove(NULL, audit_info);
+       return netlbl_domhsh_remove(NULL, family, audit_info);
 }
 
 /**
  * netlbl_domhsh_getentry - Get an entry from the domain hash table
  * @domain: the domain name to search for
+ * @family: address family
  *
  * Description:
  * Look through the domain hash table searching for an entry to match @domain,
- * return a pointer to a copy of the entry or NULL.  The caller is responsible
- * for ensuring that rcu_read_[un]lock() is called.
+ * with address family @family, return a pointer to a copy of the entry or
+ * NULL.  The caller is responsible for ensuring that rcu_read_[un]lock() is
+ * called.
  *
  */
-struct netlbl_dom_map *netlbl_domhsh_getentry(const char *domain)
+struct netlbl_dom_map *netlbl_domhsh_getentry(const char *domain, u16 family)
 {
-       return netlbl_domhsh_search_def(domain);
+       if (family == AF_UNSPEC)
+               return NULL;
+       return netlbl_domhsh_search_def(domain, family);
 }
 
 /**
@@ -696,7 +891,7 @@ struct netlbl_dommap_def *netlbl_domhsh_getentry_af4(const char *domain,
        struct netlbl_dom_map *dom_iter;
        struct netlbl_af4list *addr_iter;
 
-       dom_iter = netlbl_domhsh_search_def(domain);
+       dom_iter = netlbl_domhsh_search_def(domain, AF_INET);
        if (dom_iter == NULL)
                return NULL;
 
@@ -726,7 +921,7 @@ struct netlbl_dommap_def *netlbl_domhsh_getentry_af6(const char *domain,
        struct netlbl_dom_map *dom_iter;
        struct netlbl_af6list *addr_iter;
 
-       dom_iter = netlbl_domhsh_search_def(domain);
+       dom_iter = netlbl_domhsh_search_def(domain, AF_INET6);
        if (dom_iter == NULL)
                return NULL;
 
index 680caf4dff567f61ccaa41a70a9276101ac27fcd..1f92477819274013991533b8e74ba0b1e1e1e9ed 100644 (file)
@@ -51,6 +51,7 @@ struct netlbl_dommap_def {
        union {
                struct netlbl_domaddr_map *addrsel;
                struct cipso_v4_doi *cipso;
+               struct calipso_doi *calipso;
        };
 };
 #define netlbl_domhsh_addr4_entry(iter) \
@@ -70,6 +71,7 @@ struct netlbl_domaddr6_map {
 
 struct netlbl_dom_map {
        char *domain;
+       u16 family;
        struct netlbl_dommap_def def;
 
        u32 valid;
@@ -91,14 +93,23 @@ int netlbl_domhsh_remove_af4(const char *domain,
                             const struct in_addr *addr,
                             const struct in_addr *mask,
                             struct netlbl_audit *audit_info);
-int netlbl_domhsh_remove(const char *domain, struct netlbl_audit *audit_info);
-int netlbl_domhsh_remove_default(struct netlbl_audit *audit_info);
-struct netlbl_dom_map *netlbl_domhsh_getentry(const char *domain);
+int netlbl_domhsh_remove_af6(const char *domain,
+                            const struct in6_addr *addr,
+                            const struct in6_addr *mask,
+                            struct netlbl_audit *audit_info);
+int netlbl_domhsh_remove(const char *domain, u16 family,
+                        struct netlbl_audit *audit_info);
+int netlbl_domhsh_remove_default(u16 family, struct netlbl_audit *audit_info);
+struct netlbl_dom_map *netlbl_domhsh_getentry(const char *domain, u16 family);
 struct netlbl_dommap_def *netlbl_domhsh_getentry_af4(const char *domain,
                                                     __be32 addr);
 #if IS_ENABLED(CONFIG_IPV6)
 struct netlbl_dommap_def *netlbl_domhsh_getentry_af6(const char *domain,
                                                   const struct in6_addr *addr);
+int netlbl_domhsh_remove_af6(const char *domain,
+                            const struct in6_addr *addr,
+                            const struct in6_addr *mask,
+                            struct netlbl_audit *audit_info);
 #endif /* IPv6 */
 
 int netlbl_domhsh_walk(u32 *skip_bkt,
index 1325776daa278a1c812a5c184038eb6e69c4b5ba..28c56b95fb7ff6ebaf5a99d8cfcbf583a01e73f5 100644 (file)
 #include <net/ipv6.h>
 #include <net/netlabel.h>
 #include <net/cipso_ipv4.h>
+#include <net/calipso.h>
 #include <asm/bug.h>
 #include <linux/atomic.h>
 
 #include "netlabel_domainhash.h"
 #include "netlabel_unlabeled.h"
 #include "netlabel_cipso_v4.h"
+#include "netlabel_calipso.h"
 #include "netlabel_user.h"
 #include "netlabel_mgmt.h"
 #include "netlabel_addrlist.h"
@@ -72,12 +74,17 @@ int netlbl_cfg_map_del(const char *domain,
                       struct netlbl_audit *audit_info)
 {
        if (addr == NULL && mask == NULL) {
-               return netlbl_domhsh_remove(domain, audit_info);
+               return netlbl_domhsh_remove(domain, family, audit_info);
        } else if (addr != NULL && mask != NULL) {
                switch (family) {
                case AF_INET:
                        return netlbl_domhsh_remove_af4(domain, addr, mask,
                                                        audit_info);
+#if IS_ENABLED(CONFIG_IPV6)
+               case AF_INET6:
+                       return netlbl_domhsh_remove_af6(domain, addr, mask,
+                                                       audit_info);
+#endif /* IPv6 */
                default:
                        return -EPFNOSUPPORT;
                }
@@ -119,6 +126,7 @@ int netlbl_cfg_unlbl_map_add(const char *domain,
                if (entry->domain == NULL)
                        goto cfg_unlbl_map_add_failure;
        }
+       entry->family = family;
 
        if (addr == NULL && mask == NULL)
                entry->def.type = NETLBL_NLTYPE_UNLABELED;
@@ -345,6 +353,7 @@ int netlbl_cfg_cipsov4_map_add(u32 doi,
        entry = kzalloc(sizeof(*entry), GFP_ATOMIC);
        if (entry == NULL)
                goto out_entry;
+       entry->family = AF_INET;
        if (domain != NULL) {
                entry->domain = kstrdup(domain, GFP_ATOMIC);
                if (entry->domain == NULL)
@@ -399,6 +408,139 @@ out_entry:
        return ret_val;
 }
 
+/**
+ * netlbl_cfg_calipso_add - Add a new CALIPSO DOI definition
+ * @doi_def: CALIPSO DOI definition
+ * @audit_info: NetLabel audit information
+ *
+ * Description:
+ * Add a new CALIPSO DOI definition as defined by @doi_def.  Returns zero on
+ * success and negative values on failure.
+ *
+ */
+int netlbl_cfg_calipso_add(struct calipso_doi *doi_def,
+                          struct netlbl_audit *audit_info)
+{
+#if IS_ENABLED(CONFIG_IPV6)
+       return calipso_doi_add(doi_def, audit_info);
+#else /* IPv6 */
+       return -ENOSYS;
+#endif /* IPv6 */
+}
+
+/**
+ * netlbl_cfg_calipso_del - Remove an existing CALIPSO DOI definition
+ * @doi: CALIPSO DOI
+ * @audit_info: NetLabel audit information
+ *
+ * Description:
+ * Remove an existing CALIPSO DOI definition matching @doi.  Returns zero on
+ * success and negative values on failure.
+ *
+ */
+void netlbl_cfg_calipso_del(u32 doi, struct netlbl_audit *audit_info)
+{
+#if IS_ENABLED(CONFIG_IPV6)
+       calipso_doi_remove(doi, audit_info);
+#endif /* IPv6 */
+}
+
+/**
+ * netlbl_cfg_calipso_map_add - Add a new CALIPSO DOI mapping
+ * @doi: the CALIPSO DOI
+ * @domain: the domain mapping to add
+ * @addr: IP address
+ * @mask: IP address mask
+ * @audit_info: NetLabel audit information
+ *
+ * Description:
+ * Add a new NetLabel/LSM domain mapping for the given CALIPSO DOI to the
+ * NetLabel subsystem.  A @domain value of NULL adds a new default domain
+ * mapping.  Returns zero on success, negative values on failure.
+ *
+ */
+int netlbl_cfg_calipso_map_add(u32 doi,
+                              const char *domain,
+                              const struct in6_addr *addr,
+                              const struct in6_addr *mask,
+                              struct netlbl_audit *audit_info)
+{
+#if IS_ENABLED(CONFIG_IPV6)
+       int ret_val = -ENOMEM;
+       struct calipso_doi *doi_def;
+       struct netlbl_dom_map *entry;
+       struct netlbl_domaddr_map *addrmap = NULL;
+       struct netlbl_domaddr6_map *addrinfo = NULL;
+
+       doi_def = calipso_doi_getdef(doi);
+       if (doi_def == NULL)
+               return -ENOENT;
+
+       entry = kzalloc(sizeof(*entry), GFP_ATOMIC);
+       if (entry == NULL)
+               goto out_entry;
+       entry->family = AF_INET6;
+       if (domain != NULL) {
+               entry->domain = kstrdup(domain, GFP_ATOMIC);
+               if (entry->domain == NULL)
+                       goto out_domain;
+       }
+
+       if (addr == NULL && mask == NULL) {
+               entry->def.calipso = doi_def;
+               entry->def.type = NETLBL_NLTYPE_CALIPSO;
+       } else if (addr != NULL && mask != NULL) {
+               addrmap = kzalloc(sizeof(*addrmap), GFP_ATOMIC);
+               if (addrmap == NULL)
+                       goto out_addrmap;
+               INIT_LIST_HEAD(&addrmap->list4);
+               INIT_LIST_HEAD(&addrmap->list6);
+
+               addrinfo = kzalloc(sizeof(*addrinfo), GFP_ATOMIC);
+               if (addrinfo == NULL)
+                       goto out_addrinfo;
+               addrinfo->def.calipso = doi_def;
+               addrinfo->def.type = NETLBL_NLTYPE_CALIPSO;
+               addrinfo->list.addr = *addr;
+               addrinfo->list.addr.s6_addr32[0] &= mask->s6_addr32[0];
+               addrinfo->list.addr.s6_addr32[1] &= mask->s6_addr32[1];
+               addrinfo->list.addr.s6_addr32[2] &= mask->s6_addr32[2];
+               addrinfo->list.addr.s6_addr32[3] &= mask->s6_addr32[3];
+               addrinfo->list.mask = *mask;
+               addrinfo->list.valid = 1;
+               ret_val = netlbl_af6list_add(&addrinfo->list, &addrmap->list6);
+               if (ret_val != 0)
+                       goto cfg_calipso_map_add_failure;
+
+               entry->def.addrsel = addrmap;
+               entry->def.type = NETLBL_NLTYPE_ADDRSELECT;
+       } else {
+               ret_val = -EINVAL;
+               goto out_addrmap;
+       }
+
+       ret_val = netlbl_domhsh_add(entry, audit_info);
+       if (ret_val != 0)
+               goto cfg_calipso_map_add_failure;
+
+       return 0;
+
+cfg_calipso_map_add_failure:
+       kfree(addrinfo);
+out_addrinfo:
+       kfree(addrmap);
+out_addrmap:
+       kfree(entry->domain);
+out_domain:
+       kfree(entry);
+out_entry:
+       calipso_doi_putdef(doi_def);
+       return ret_val;
+#else /* IPv6 */
+       return -ENOSYS;
+#endif /* IPv6 */
+}
+
 /*
  * Security Attribute Functions
  */
@@ -519,6 +661,7 @@ int netlbl_catmap_walk(struct netlbl_lsm_catmap *catmap, u32 offset)
 
        return -ENOENT;
 }
+EXPORT_SYMBOL(netlbl_catmap_walk);
 
 /**
  * netlbl_catmap_walkrng - Find the end of a string of set bits
@@ -609,20 +752,19 @@ int netlbl_catmap_getlong(struct netlbl_lsm_catmap *catmap,
                off = catmap->startbit;
                *offset = off;
        }
-       iter = _netlbl_catmap_getnode(&catmap, off, _CM_F_NONE, 0);
+       iter = _netlbl_catmap_getnode(&catmap, off, _CM_F_WALK, 0);
        if (iter == NULL) {
                *offset = (u32)-1;
                return 0;
        }
 
        if (off < iter->startbit) {
-               off = iter->startbit;
-               *offset = off;
+               *offset = iter->startbit;
+               off = 0;
        } else
                off -= iter->startbit;
-
        idx = off / NETLBL_CATMAP_MAPSIZE;
-       *bitmap = iter->bitmap[idx] >> (off % NETLBL_CATMAP_SIZE);
+       *bitmap = iter->bitmap[idx] >> (off % NETLBL_CATMAP_MAPSIZE);
 
        return 0;
 }
@@ -655,6 +797,7 @@ int netlbl_catmap_setbit(struct netlbl_lsm_catmap **catmap,
 
        return 0;
 }
+EXPORT_SYMBOL(netlbl_catmap_setbit);
 
 /**
  * netlbl_catmap_setrng - Set a range of bits in a LSM secattr catmap
@@ -727,6 +870,76 @@ int netlbl_catmap_setlong(struct netlbl_lsm_catmap **catmap,
        return 0;
 }
 
+/* Bitmap functions
+ */
+
+/**
+ * netlbl_bitmap_walk - Walk a bitmap looking for a bit
+ * @bitmap: the bitmap
+ * @bitmap_len: length in bits
+ * @offset: starting offset
+ * @state: if non-zero, look for a set (1) bit else look for a cleared (0) bit
+ *
+ * Description:
+ * Starting at @offset, walk the bitmap from left to right until either the
+ * desired bit is found or we reach the end.  Return the bit offset, -1 if
+ * not found, or -2 if error.
+ */
+int netlbl_bitmap_walk(const unsigned char *bitmap, u32 bitmap_len,
+                      u32 offset, u8 state)
+{
+       u32 bit_spot;
+       u32 byte_offset;
+       unsigned char bitmask;
+       unsigned char byte;
+
+       byte_offset = offset / 8;
+       byte = bitmap[byte_offset];
+       bit_spot = offset;
+       bitmask = 0x80 >> (offset % 8);
+
+       while (bit_spot < bitmap_len) {
+               if ((state && (byte & bitmask) == bitmask) ||
+                   (state == 0 && (byte & bitmask) == 0))
+                       return bit_spot;
+
+               bit_spot++;
+               bitmask >>= 1;
+               if (bitmask == 0) {
+                       byte = bitmap[++byte_offset];
+                       bitmask = 0x80;
+               }
+       }
+
+       return -1;
+}
+EXPORT_SYMBOL(netlbl_bitmap_walk);
+
+/**
+ * netlbl_bitmap_setbit - Sets a single bit in a bitmap
+ * @bitmap: the bitmap
+ * @bit: the bit
+ * @state: if non-zero, set the bit (1) else clear the bit (0)
+ *
+ * Description:
+ * Set a single bit in the bitmask.  Returns zero on success, negative values
+ * on error.
+ */
+void netlbl_bitmap_setbit(unsigned char *bitmap, u32 bit, u8 state)
+{
+       u32 byte_spot;
+       u8 bitmask;
+
+       /* gcc always rounds to zero when doing integer division */
+       byte_spot = bit / 8;
+       bitmask = 0x80 >> (bit % 8);
+       if (state)
+               bitmap[byte_spot] |= bitmask;
+       else
+               bitmap[byte_spot] &= ~bitmask;
+}
+EXPORT_SYMBOL(netlbl_bitmap_setbit);
+
 /*
  * LSM Functions
  */
@@ -774,7 +987,7 @@ int netlbl_sock_setattr(struct sock *sk,
        struct netlbl_dom_map *dom_entry;
 
        rcu_read_lock();
-       dom_entry = netlbl_domhsh_getentry(secattr->domain);
+       dom_entry = netlbl_domhsh_getentry(secattr->domain, family);
        if (dom_entry == NULL) {
                ret_val = -ENOENT;
                goto socket_setattr_return;
@@ -799,9 +1012,21 @@ int netlbl_sock_setattr(struct sock *sk,
                break;
 #if IS_ENABLED(CONFIG_IPV6)
        case AF_INET6:
-               /* since we don't support any IPv6 labeling protocols right
-                * now we can optimize everything away until we do */
-               ret_val = 0;
+               switch (dom_entry->def.type) {
+               case NETLBL_NLTYPE_ADDRSELECT:
+                       ret_val = -EDESTADDRREQ;
+                       break;
+               case NETLBL_NLTYPE_CALIPSO:
+                       ret_val = calipso_sock_setattr(sk,
+                                                      dom_entry->def.calipso,
+                                                      secattr);
+                       break;
+               case NETLBL_NLTYPE_UNLABELED:
+                       ret_val = 0;
+                       break;
+               default:
+                       ret_val = -ENOENT;
+               }
                break;
 #endif /* IPv6 */
        default:
@@ -824,7 +1049,16 @@ socket_setattr_return:
  */
 void netlbl_sock_delattr(struct sock *sk)
 {
-       cipso_v4_sock_delattr(sk);
+       switch (sk->sk_family) {
+       case AF_INET:
+               cipso_v4_sock_delattr(sk);
+               break;
+#if IS_ENABLED(CONFIG_IPV6)
+       case AF_INET6:
+               calipso_sock_delattr(sk);
+               break;
+#endif /* IPv6 */
+       }
 }
 
 /**
@@ -850,7 +1084,7 @@ int netlbl_sock_getattr(struct sock *sk,
                break;
 #if IS_ENABLED(CONFIG_IPV6)
        case AF_INET6:
-               ret_val = -ENOMSG;
+               ret_val = calipso_sock_getattr(sk, secattr);
                break;
 #endif /* IPv6 */
        default:
@@ -878,6 +1112,9 @@ int netlbl_conn_setattr(struct sock *sk,
 {
        int ret_val;
        struct sockaddr_in *addr4;
+#if IS_ENABLED(CONFIG_IPV6)
+       struct sockaddr_in6 *addr6;
+#endif
        struct netlbl_dommap_def *entry;
 
        rcu_read_lock();
@@ -898,7 +1135,7 @@ int netlbl_conn_setattr(struct sock *sk,
                case NETLBL_NLTYPE_UNLABELED:
                        /* just delete the protocols we support for right now
                         * but we could remove other protocols if needed */
-                       cipso_v4_sock_delattr(sk);
+                       netlbl_sock_delattr(sk);
                        ret_val = 0;
                        break;
                default:
@@ -907,9 +1144,27 @@ int netlbl_conn_setattr(struct sock *sk,
                break;
 #if IS_ENABLED(CONFIG_IPV6)
        case AF_INET6:
-               /* since we don't support any IPv6 labeling protocols right
-                * now we can optimize everything away until we do */
-               ret_val = 0;
+               addr6 = (struct sockaddr_in6 *)addr;
+               entry = netlbl_domhsh_getentry_af6(secattr->domain,
+                                                  &addr6->sin6_addr);
+               if (entry == NULL) {
+                       ret_val = -ENOENT;
+                       goto conn_setattr_return;
+               }
+               switch (entry->type) {
+               case NETLBL_NLTYPE_CALIPSO:
+                       ret_val = calipso_sock_setattr(sk,
+                                                      entry->calipso, secattr);
+                       break;
+               case NETLBL_NLTYPE_UNLABELED:
+                       /* just delete the protocols we support for right now
+                        * but we could remove other protocols if needed */
+                       netlbl_sock_delattr(sk);
+                       ret_val = 0;
+                       break;
+               default:
+                       ret_val = -ENOENT;
+               }
                break;
 #endif /* IPv6 */
        default:
@@ -936,12 +1191,13 @@ int netlbl_req_setattr(struct request_sock *req,
 {
        int ret_val;
        struct netlbl_dommap_def *entry;
+       struct inet_request_sock *ireq = inet_rsk(req);
 
        rcu_read_lock();
        switch (req->rsk_ops->family) {
        case AF_INET:
                entry = netlbl_domhsh_getentry_af4(secattr->domain,
-                                                  inet_rsk(req)->ir_rmt_addr);
+                                                  ireq->ir_rmt_addr);
                if (entry == NULL) {
                        ret_val = -ENOENT;
                        goto req_setattr_return;
@@ -952,9 +1208,7 @@ int netlbl_req_setattr(struct request_sock *req,
                                                       entry->cipso, secattr);
                        break;
                case NETLBL_NLTYPE_UNLABELED:
-                       /* just delete the protocols we support for right now
-                        * but we could remove other protocols if needed */
-                       cipso_v4_req_delattr(req);
+                       netlbl_req_delattr(req);
                        ret_val = 0;
                        break;
                default:
@@ -963,9 +1217,24 @@ int netlbl_req_setattr(struct request_sock *req,
                break;
 #if IS_ENABLED(CONFIG_IPV6)
        case AF_INET6:
-               /* since we don't support any IPv6 labeling protocols right
-                * now we can optimize everything away until we do */
-               ret_val = 0;
+               entry = netlbl_domhsh_getentry_af6(secattr->domain,
+                                                  &ireq->ir_v6_rmt_addr);
+               if (entry == NULL) {
+                       ret_val = -ENOENT;
+                       goto req_setattr_return;
+               }
+               switch (entry->type) {
+               case NETLBL_NLTYPE_CALIPSO:
+                       ret_val = calipso_req_setattr(req,
+                                                     entry->calipso, secattr);
+                       break;
+               case NETLBL_NLTYPE_UNLABELED:
+                       netlbl_req_delattr(req);
+                       ret_val = 0;
+                       break;
+               default:
+                       ret_val = -ENOENT;
+               }
                break;
 #endif /* IPv6 */
        default:
@@ -987,7 +1256,16 @@ req_setattr_return:
 */
 void netlbl_req_delattr(struct request_sock *req)
 {
-       cipso_v4_req_delattr(req);
+       switch (req->rsk_ops->family) {
+       case AF_INET:
+               cipso_v4_req_delattr(req);
+               break;
+#if IS_ENABLED(CONFIG_IPV6)
+       case AF_INET6:
+               calipso_req_delattr(req);
+               break;
+#endif /* IPv6 */
+       }
 }
 
 /**
@@ -1007,13 +1285,17 @@ int netlbl_skbuff_setattr(struct sk_buff *skb,
 {
        int ret_val;
        struct iphdr *hdr4;
+#if IS_ENABLED(CONFIG_IPV6)
+       struct ipv6hdr *hdr6;
+#endif
        struct netlbl_dommap_def *entry;
 
        rcu_read_lock();
        switch (family) {
        case AF_INET:
                hdr4 = ip_hdr(skb);
-               entry = netlbl_domhsh_getentry_af4(secattr->domain,hdr4->daddr);
+               entry = netlbl_domhsh_getentry_af4(secattr->domain,
+                                                  hdr4->daddr);
                if (entry == NULL) {
                        ret_val = -ENOENT;
                        goto skbuff_setattr_return;
@@ -1034,9 +1316,26 @@ int netlbl_skbuff_setattr(struct sk_buff *skb,
                break;
 #if IS_ENABLED(CONFIG_IPV6)
        case AF_INET6:
-               /* since we don't support any IPv6 labeling protocols right
-                * now we can optimize everything away until we do */
-               ret_val = 0;
+               hdr6 = ipv6_hdr(skb);
+               entry = netlbl_domhsh_getentry_af6(secattr->domain,
+                                                  &hdr6->daddr);
+               if (entry == NULL) {
+                       ret_val = -ENOENT;
+                       goto skbuff_setattr_return;
+               }
+               switch (entry->type) {
+               case NETLBL_NLTYPE_CALIPSO:
+                       ret_val = calipso_skbuff_setattr(skb, entry->calipso,
+                                                        secattr);
+                       break;
+               case NETLBL_NLTYPE_UNLABELED:
+                       /* just delete the protocols we support for right now
+                        * but we could remove other protocols if needed */
+                       ret_val = calipso_skbuff_delattr(skb);
+                       break;
+               default:
+                       ret_val = -ENOENT;
+               }
                break;
 #endif /* IPv6 */
        default:
@@ -1075,6 +1374,9 @@ int netlbl_skbuff_getattr(const struct sk_buff *skb,
                break;
 #if IS_ENABLED(CONFIG_IPV6)
        case AF_INET6:
+               ptr = calipso_optptr(skb);
+               if (ptr && calipso_getattr(ptr, secattr) == 0)
+                       return 0;
                break;
 #endif /* IPv6 */
        }
@@ -1085,6 +1387,7 @@ int netlbl_skbuff_getattr(const struct sk_buff *skb,
 /**
  * netlbl_skbuff_err - Handle a LSM error on a sk_buff
  * @skb: the packet
+ * @family: the family
  * @error: the error code
  * @gateway: true if host is acting as a gateway, false otherwise
  *
@@ -1094,10 +1397,14 @@ int netlbl_skbuff_getattr(const struct sk_buff *skb,
  * according to the packet's labeling protocol.
  *
  */
-void netlbl_skbuff_err(struct sk_buff *skb, int error, int gateway)
+void netlbl_skbuff_err(struct sk_buff *skb, u16 family, int error, int gateway)
 {
-       if (cipso_v4_optptr(skb))
-               cipso_v4_error(skb, error, gateway);
+       switch (family) {
+       case AF_INET:
+               if (cipso_v4_optptr(skb))
+                       cipso_v4_error(skb, error, gateway);
+               break;
+       }
 }
 
 /**
@@ -1112,11 +1419,15 @@ void netlbl_skbuff_err(struct sk_buff *skb, int error, int gateway)
 void netlbl_cache_invalidate(void)
 {
        cipso_v4_cache_invalidate();
+#if IS_ENABLED(CONFIG_IPV6)
+       calipso_cache_invalidate();
+#endif /* IPv6 */
 }
 
 /**
  * netlbl_cache_add - Add an entry to a NetLabel protocol cache
  * @skb: the packet
+ * @family: the family
  * @secattr: the packet's security attributes
  *
  * Description:
@@ -1125,7 +1436,7 @@ void netlbl_cache_invalidate(void)
  * values on error.
  *
  */
-int netlbl_cache_add(const struct sk_buff *skb,
+int netlbl_cache_add(const struct sk_buff *skb, u16 family,
                     const struct netlbl_lsm_secattr *secattr)
 {
        unsigned char *ptr;
@@ -1133,10 +1444,20 @@ int netlbl_cache_add(const struct sk_buff *skb,
        if ((secattr->flags & NETLBL_SECATTR_CACHE) == 0)
                return -ENOMSG;
 
-       ptr = cipso_v4_optptr(skb);
-       if (ptr)
-               return cipso_v4_cache_add(ptr, secattr);
-
+       switch (family) {
+       case AF_INET:
+               ptr = cipso_v4_optptr(skb);
+               if (ptr)
+                       return cipso_v4_cache_add(ptr, secattr);
+               break;
+#if IS_ENABLED(CONFIG_IPV6)
+       case AF_INET6:
+               ptr = calipso_optptr(skb);
+               if (ptr)
+                       return calipso_cache_add(ptr, secattr);
+               break;
+#endif /* IPv6 */
+       }
        return -ENOMSG;
 }
 
@@ -1161,6 +1482,7 @@ struct audit_buffer *netlbl_audit_start(int type,
 {
        return netlbl_audit_start_common(type, audit_info);
 }
+EXPORT_SYMBOL(netlbl_audit_start);
 
 /*
  * Setup Functions
index 13f777f20995bbe02edbce36a9c04f2518fe0355..f85d0e07af2dd9c7f4f4fb2f6b05fcf99b21c8eb 100644 (file)
 #include <net/ipv6.h>
 #include <net/netlabel.h>
 #include <net/cipso_ipv4.h>
+#include <net/calipso.h>
 #include <linux/atomic.h>
 
+#include "netlabel_calipso.h"
 #include "netlabel_domainhash.h"
 #include "netlabel_user.h"
 #include "netlabel_mgmt.h"
@@ -72,6 +74,8 @@ static const struct nla_policy netlbl_mgmt_genl_policy[NLBL_MGMT_A_MAX + 1] = {
        [NLBL_MGMT_A_PROTOCOL] = { .type = NLA_U32 },
        [NLBL_MGMT_A_VERSION] = { .type = NLA_U32 },
        [NLBL_MGMT_A_CV4DOI] = { .type = NLA_U32 },
+       [NLBL_MGMT_A_FAMILY] = { .type = NLA_U16 },
+       [NLBL_MGMT_A_CLPDOI] = { .type = NLA_U32 },
 };
 
 /*
@@ -95,6 +99,9 @@ static int netlbl_mgmt_add_common(struct genl_info *info,
        int ret_val = -EINVAL;
        struct netlbl_domaddr_map *addrmap = NULL;
        struct cipso_v4_doi *cipsov4 = NULL;
+#if IS_ENABLED(CONFIG_IPV6)
+       struct calipso_doi *calipso = NULL;
+#endif
        u32 tmp_val;
        struct netlbl_dom_map *entry = kzalloc(sizeof(*entry), GFP_KERNEL);
 
@@ -119,6 +126,11 @@ static int netlbl_mgmt_add_common(struct genl_info *info,
 
        switch (entry->def.type) {
        case NETLBL_NLTYPE_UNLABELED:
+               if (info->attrs[NLBL_MGMT_A_FAMILY])
+                       entry->family =
+                               nla_get_u16(info->attrs[NLBL_MGMT_A_FAMILY]);
+               else
+                       entry->family = AF_UNSPEC;
                break;
        case NETLBL_NLTYPE_CIPSOV4:
                if (!info->attrs[NLBL_MGMT_A_CV4DOI])
@@ -128,12 +140,30 @@ static int netlbl_mgmt_add_common(struct genl_info *info,
                cipsov4 = cipso_v4_doi_getdef(tmp_val);
                if (cipsov4 == NULL)
                        goto add_free_domain;
+               entry->family = AF_INET;
                entry->def.cipso = cipsov4;
                break;
+#if IS_ENABLED(CONFIG_IPV6)
+       case NETLBL_NLTYPE_CALIPSO:
+               if (!info->attrs[NLBL_MGMT_A_CLPDOI])
+                       goto add_free_domain;
+
+               tmp_val = nla_get_u32(info->attrs[NLBL_MGMT_A_CLPDOI]);
+               calipso = calipso_doi_getdef(tmp_val);
+               if (calipso == NULL)
+                       goto add_free_domain;
+               entry->family = AF_INET6;
+               entry->def.calipso = calipso;
+               break;
+#endif /* IPv6 */
        default:
                goto add_free_domain;
        }
 
+       if ((entry->family == AF_INET && info->attrs[NLBL_MGMT_A_IPV6ADDR]) ||
+           (entry->family == AF_INET6 && info->attrs[NLBL_MGMT_A_IPV4ADDR]))
+               goto add_doi_put_def;
+
        if (info->attrs[NLBL_MGMT_A_IPV4ADDR]) {
                struct in_addr *addr;
                struct in_addr *mask;
@@ -178,6 +208,7 @@ static int netlbl_mgmt_add_common(struct genl_info *info,
                        goto add_free_addrmap;
                }
 
+               entry->family = AF_INET;
                entry->def.type = NETLBL_NLTYPE_ADDRSELECT;
                entry->def.addrsel = addrmap;
 #if IS_ENABLED(CONFIG_IPV6)
@@ -220,6 +251,8 @@ static int netlbl_mgmt_add_common(struct genl_info *info,
                map->list.mask = *mask;
                map->list.valid = 1;
                map->def.type = entry->def.type;
+               if (calipso)
+                       map->def.calipso = calipso;
 
                ret_val = netlbl_af6list_add(&map->list, &addrmap->list6);
                if (ret_val != 0) {
@@ -227,6 +260,7 @@ static int netlbl_mgmt_add_common(struct genl_info *info,
                        goto add_free_addrmap;
                }
 
+               entry->family = AF_INET6;
                entry->def.type = NETLBL_NLTYPE_ADDRSELECT;
                entry->def.addrsel = addrmap;
 #endif /* IPv6 */
@@ -242,6 +276,9 @@ add_free_addrmap:
        kfree(addrmap);
 add_doi_put_def:
        cipso_v4_doi_putdef(cipsov4);
+#if IS_ENABLED(CONFIG_IPV6)
+       calipso_doi_putdef(calipso);
+#endif
 add_free_domain:
        kfree(entry->domain);
 add_free_entry:
@@ -278,6 +315,10 @@ static int netlbl_mgmt_listentry(struct sk_buff *skb,
                        return ret_val;
        }
 
+       ret_val = nla_put_u16(skb, NLBL_MGMT_A_FAMILY, entry->family);
+       if (ret_val != 0)
+               return ret_val;
+
        switch (entry->def.type) {
        case NETLBL_NLTYPE_ADDRSELECT:
                nla_a = nla_nest_start(skb, NLBL_MGMT_A_SELECTORLIST);
@@ -340,6 +381,15 @@ static int netlbl_mgmt_listentry(struct sk_buff *skb,
                        if (ret_val != 0)
                                return ret_val;
 
+                       switch (map6->def.type) {
+                       case NETLBL_NLTYPE_CALIPSO:
+                               ret_val = nla_put_u32(skb, NLBL_MGMT_A_CLPDOI,
+                                                     map6->def.calipso->doi);
+                               if (ret_val != 0)
+                                       return ret_val;
+                               break;
+                       }
+
                        nla_nest_end(skb, nla_b);
                }
 #endif /* IPv6 */
@@ -347,15 +397,25 @@ static int netlbl_mgmt_listentry(struct sk_buff *skb,
                nla_nest_end(skb, nla_a);
                break;
        case NETLBL_NLTYPE_UNLABELED:
-               ret_val = nla_put_u32(skb,NLBL_MGMT_A_PROTOCOL,entry->def.type);
+               ret_val = nla_put_u32(skb, NLBL_MGMT_A_PROTOCOL,
+                                     entry->def.type);
                break;
        case NETLBL_NLTYPE_CIPSOV4:
-               ret_val = nla_put_u32(skb,NLBL_MGMT_A_PROTOCOL,entry->def.type);
+               ret_val = nla_put_u32(skb, NLBL_MGMT_A_PROTOCOL,
+                                     entry->def.type);
                if (ret_val != 0)
                        return ret_val;
                ret_val = nla_put_u32(skb, NLBL_MGMT_A_CV4DOI,
                                      entry->def.cipso->doi);
                break;
+       case NETLBL_NLTYPE_CALIPSO:
+               ret_val = nla_put_u32(skb, NLBL_MGMT_A_PROTOCOL,
+                                     entry->def.type);
+               if (ret_val != 0)
+                       return ret_val;
+               ret_val = nla_put_u32(skb, NLBL_MGMT_A_CLPDOI,
+                                     entry->def.calipso->doi);
+               break;
        }
 
        return ret_val;
@@ -418,7 +478,7 @@ static int netlbl_mgmt_remove(struct sk_buff *skb, struct genl_info *info)
        netlbl_netlink_auditinfo(skb, &audit_info);
 
        domain = nla_data(info->attrs[NLBL_MGMT_A_DOMAIN]);
-       return netlbl_domhsh_remove(domain, &audit_info);
+       return netlbl_domhsh_remove(domain, AF_UNSPEC, &audit_info);
 }
 
 /**
@@ -536,7 +596,7 @@ static int netlbl_mgmt_removedef(struct sk_buff *skb, struct genl_info *info)
 
        netlbl_netlink_auditinfo(skb, &audit_info);
 
-       return netlbl_domhsh_remove_default(&audit_info);
+       return netlbl_domhsh_remove_default(AF_UNSPEC, &audit_info);
 }
 
 /**
@@ -556,6 +616,12 @@ static int netlbl_mgmt_listdef(struct sk_buff *skb, struct genl_info *info)
        struct sk_buff *ans_skb = NULL;
        void *data;
        struct netlbl_dom_map *entry;
+       u16 family;
+
+       if (info->attrs[NLBL_MGMT_A_FAMILY])
+               family = nla_get_u16(info->attrs[NLBL_MGMT_A_FAMILY]);
+       else
+               family = AF_INET;
 
        ans_skb = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
        if (ans_skb == NULL)
@@ -566,7 +632,7 @@ static int netlbl_mgmt_listdef(struct sk_buff *skb, struct genl_info *info)
                goto listdef_failure;
 
        rcu_read_lock();
-       entry = netlbl_domhsh_getentry(NULL);
+       entry = netlbl_domhsh_getentry(NULL, family);
        if (entry == NULL) {
                ret_val = -ENOENT;
                goto listdef_failure_lock;
@@ -651,6 +717,15 @@ static int netlbl_mgmt_protocols(struct sk_buff *skb,
                        goto protocols_return;
                protos_sent++;
        }
+#if IS_ENABLED(CONFIG_IPV6)
+       if (protos_sent == 2) {
+               if (netlbl_mgmt_protocols_cb(skb,
+                                            cb,
+                                            NETLBL_NLTYPE_CALIPSO) < 0)
+                       goto protocols_return;
+               protos_sent++;
+       }
+#endif
 
 protocols_return:
        cb->args[0] = protos_sent;
index 8b6e1ab62b485729dca126c8ab6a82671623d1fe..ea01e42bca783a8e02b93855a2e35f2b3bb3ba10 100644 (file)
  *
  *     NLBL_MGMT_A_CV4DOI
  *
- *   If using NETLBL_NLTYPE_UNLABELED no other attributes are required.
+ *   If using NETLBL_NLTYPE_UNLABELED no other attributes are required,
+ *   however the following attribute may optionally be sent:
+ *
+ *     NLBL_MGMT_A_FAMILY
  *
  * o REMOVE:
  *   Sent by an application to remove a domain mapping from the NetLabel
@@ -77,6 +80,7 @@
  *   Required attributes:
  *
  *     NLBL_MGMT_A_DOMAIN
+ *     NLBL_MGMT_A_FAMILY
  *
  *   If the IP address selectors are not used the following attribute is
  *   required:
  *
  *     NLBL_MGMT_A_CV4DOI
  *
- *   If using NETLBL_NLTYPE_UNLABELED no other attributes are required.
+ *   If using NETLBL_NLTYPE_UNLABELED no other attributes are required,
+ *   however the following attribute may optionally be sent:
+ *
+ *     NLBL_MGMT_A_FAMILY
  *
  * o REMOVEDEF:
  *   Sent by an application to remove the default domain mapping from the
  * o LISTDEF:
  *   This message can be sent either from an application or by the kernel in
  *   response to an application generated LISTDEF message.  When sent by an
- *   application there is no payload.  On success the kernel should send a
- *   response using the following format.
+ *   application there may be an optional payload.
  *
- *   If the IP address selectors are not used the following attribute is
+ *     NLBL_MGMT_A_FAMILY
+ *
+ *   On success the kernel should send a response using the following format:
+ *
+ *   If the IP address selectors are not used the following attributes are
  *   required:
  *
  *     NLBL_MGMT_A_PROTOCOL
+ *     NLBL_MGMT_A_FAMILY
  *
  *   If the IP address selectors are used then the following attritbute is
  *   required:
@@ -209,6 +220,12 @@ enum {
        /* (NLA_NESTED)
         * the selector list, there must be at least one
         * NLBL_MGMT_A_ADDRSELECTOR attribute */
+       NLBL_MGMT_A_FAMILY,
+       /* (NLA_U16)
+        * The address family */
+       NLBL_MGMT_A_CLPDOI,
+       /* (NLA_U32)
+        * the CALIPSO DOI value */
        __NLBL_MGMT_A_MAX,
 };
 #define NLBL_MGMT_A_MAX (__NLBL_MGMT_A_MAX - 1)
index 9eaa9a1e862960e7d37c812d17f16e6680772429..4528cff9138b5499f7577bf2801dac8f85e0fcda 100644 (file)
@@ -116,8 +116,8 @@ struct netlbl_unlhsh_walk_arg {
 static DEFINE_SPINLOCK(netlbl_unlhsh_lock);
 #define netlbl_unlhsh_rcu_deref(p) \
        rcu_dereference_check(p, lockdep_is_held(&netlbl_unlhsh_lock))
-static struct netlbl_unlhsh_tbl *netlbl_unlhsh;
-static struct netlbl_unlhsh_iface *netlbl_unlhsh_def;
+static struct netlbl_unlhsh_tbl __rcu *netlbl_unlhsh;
+static struct netlbl_unlhsh_iface __rcu *netlbl_unlhsh_def;
 
 /* Accept unlabeled packets flag */
 static u8 netlabel_unlabel_acceptflg;
@@ -1537,6 +1537,7 @@ int __init netlbl_unlabel_defconf(void)
        entry = kzalloc(sizeof(*entry), GFP_KERNEL);
        if (entry == NULL)
                return -ENOMEM;
+       entry->family = AF_UNSPEC;
        entry->def.type = NETLBL_NLTYPE_UNLABELED;
        ret_val = netlbl_domhsh_add_default(entry, &audit_info);
        if (ret_val != 0)
index adf8b7900da2ac131c8ebc61d60d1373493d30c4..58495f44c62a605ea7337056ac96a1635a8225c2 100644 (file)
@@ -44,6 +44,7 @@
 #include "netlabel_mgmt.h"
 #include "netlabel_unlabeled.h"
 #include "netlabel_cipso_v4.h"
+#include "netlabel_calipso.h"
 #include "netlabel_user.h"
 
 /*
@@ -71,6 +72,10 @@ int __init netlbl_netlink_init(void)
        if (ret_val != 0)
                return ret_val;
 
+       ret_val = netlbl_calipso_genl_init();
+       if (ret_val != 0)
+               return ret_val;
+
        return netlbl_unlabel_genl_init();
 }
 
index a86d537eb79b149a7dfe1536a243e180f4b9ec92..da934342a39f59432bde76a4755f85d5082f7ea1 100644 (file)
@@ -4604,13 +4604,13 @@ static int selinux_socket_sock_rcv_skb(struct sock *sk, struct sk_buff *skb)
                err = selinux_inet_sys_rcv_skb(sock_net(sk), skb->skb_iif,
                                               addrp, family, peer_sid, &ad);
                if (err) {
-                       selinux_netlbl_err(skb, err, 0);
+                       selinux_netlbl_err(skb, family, err, 0);
                        return err;
                }
                err = avc_has_perm(sk_sid, peer_sid, SECCLASS_PEER,
                                   PEER__RECV, &ad);
                if (err) {
-                       selinux_netlbl_err(skb, err, 0);
+                       selinux_netlbl_err(skb, family, err, 0);
                        return err;
                }
        }
@@ -4978,7 +4978,7 @@ static unsigned int selinux_ip_forward(struct sk_buff *skb,
                err = selinux_inet_sys_rcv_skb(dev_net(indev), indev->ifindex,
                                               addrp, family, peer_sid, &ad);
                if (err) {
-                       selinux_netlbl_err(skb, err, 1);
+                       selinux_netlbl_err(skb, family, err, 1);
                        return NF_DROP;
                }
        }
@@ -5064,6 +5064,15 @@ static unsigned int selinux_ipv4_output(void *priv,
        return selinux_ip_output(skb, PF_INET);
 }
 
+#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
+static unsigned int selinux_ipv6_output(void *priv,
+                                       struct sk_buff *skb,
+                                       const struct nf_hook_state *state)
+{
+       return selinux_ip_output(skb, PF_INET6);
+}
+#endif /* IPV6 */
+
 static unsigned int selinux_ip_postroute_compat(struct sk_buff *skb,
                                                int ifindex,
                                                u16 family)
@@ -6298,6 +6307,12 @@ static struct nf_hook_ops selinux_nf_ops[] = {
                .hooknum =      NF_INET_FORWARD,
                .priority =     NF_IP6_PRI_SELINUX_FIRST,
        },
+       {
+               .hook =         selinux_ipv6_output,
+               .pf =           NFPROTO_IPV6,
+               .hooknum =      NF_INET_LOCAL_OUT,
+               .priority =     NF_IP6_PRI_SELINUX_FIRST,
+       },
 #endif /* IPV6 */
 };
 
index 8c59b8f150e888edf09195dedd1c687f9b0784fe..75686d53df074fcf542d5ae11518528148d4a0e7 100644 (file)
@@ -40,7 +40,8 @@
 #ifdef CONFIG_NETLABEL
 void selinux_netlbl_cache_invalidate(void);
 
-void selinux_netlbl_err(struct sk_buff *skb, int error, int gateway);
+void selinux_netlbl_err(struct sk_buff *skb, u16 family, int error,
+                       int gateway);
 
 void selinux_netlbl_sk_security_free(struct sk_security_struct *sksec);
 void selinux_netlbl_sk_security_reset(struct sk_security_struct *sksec);
@@ -72,6 +73,7 @@ static inline void selinux_netlbl_cache_invalidate(void)
 }
 
 static inline void selinux_netlbl_err(struct sk_buff *skb,
+                                     u16 family,
                                      int error,
                                      int gateway)
 {
index 1f989a539fd4abe08e3797b5d49f7b26ded4fe41..aaba6677ee2e62c84cfe7211803c1b077294d00c 100644 (file)
@@ -54,6 +54,7 @@
  *
  */
 static int selinux_netlbl_sidlookup_cached(struct sk_buff *skb,
+                                          u16 family,
                                           struct netlbl_lsm_secattr *secattr,
                                           u32 *sid)
 {
@@ -63,7 +64,7 @@ static int selinux_netlbl_sidlookup_cached(struct sk_buff *skb,
        if (rc == 0 &&
            (secattr->flags & NETLBL_SECATTR_CACHEABLE) &&
            (secattr->flags & NETLBL_SECATTR_CACHE))
-               netlbl_cache_add(skb, secattr);
+               netlbl_cache_add(skb, family, secattr);
 
        return rc;
 }
@@ -151,9 +152,9 @@ void selinux_netlbl_cache_invalidate(void)
  * present on the packet, NetLabel is smart enough to only act when it should.
  *
  */
-void selinux_netlbl_err(struct sk_buff *skb, int error, int gateway)
+void selinux_netlbl_err(struct sk_buff *skb, u16 family, int error, int gateway)
 {
-       netlbl_skbuff_err(skb, error, gateway);
+       netlbl_skbuff_err(skb, family, error, gateway);
 }
 
 /**
@@ -214,7 +215,8 @@ int selinux_netlbl_skbuff_getsid(struct sk_buff *skb,
        netlbl_secattr_init(&secattr);
        rc = netlbl_skbuff_getattr(skb, family, &secattr);
        if (rc == 0 && secattr.flags != NETLBL_SECATTR_NONE)
-               rc = selinux_netlbl_sidlookup_cached(skb, &secattr, sid);
+               rc = selinux_netlbl_sidlookup_cached(skb, family,
+                                                    &secattr, sid);
        else
                *sid = SECSID_NULL;
        *type = secattr.type;
@@ -284,7 +286,7 @@ int selinux_netlbl_inet_conn_request(struct request_sock *req, u16 family)
        int rc;
        struct netlbl_lsm_secattr secattr;
 
-       if (family != PF_INET)
+       if (family != PF_INET && family != PF_INET6)
                return 0;
 
        netlbl_secattr_init(&secattr);
@@ -333,7 +335,7 @@ int selinux_netlbl_socket_post_create(struct sock *sk, u16 family)
        struct sk_security_struct *sksec = sk->sk_security;
        struct netlbl_lsm_secattr *secattr;
 
-       if (family != PF_INET)
+       if (family != PF_INET && family != PF_INET6)
                return 0;
 
        secattr = selinux_netlbl_sock_genattr(sk);
@@ -382,7 +384,8 @@ int selinux_netlbl_sock_rcv_skb(struct sk_security_struct *sksec,
        netlbl_secattr_init(&secattr);
        rc = netlbl_skbuff_getattr(skb, family, &secattr);
        if (rc == 0 && secattr.flags != NETLBL_SECATTR_NONE)
-               rc = selinux_netlbl_sidlookup_cached(skb, &secattr, &nlbl_sid);
+               rc = selinux_netlbl_sidlookup_cached(skb, family,
+                                                    &secattr, &nlbl_sid);
        else
                nlbl_sid = SECINITSID_UNLABELED;
        netlbl_secattr_destroy(&secattr);
@@ -405,10 +408,25 @@ int selinux_netlbl_sock_rcv_skb(struct sk_security_struct *sksec,
                return 0;
 
        if (nlbl_sid != SECINITSID_UNLABELED)
-               netlbl_skbuff_err(skb, rc, 0);
+               netlbl_skbuff_err(skb, family, rc, 0);
        return rc;
 }
 
+/**
+ * selinux_netlbl_option - Is this a NetLabel option
+ * @level: the socket level or protocol
+ * @optname: the socket option name
+ *
+ * Description:
+ * Returns true if @level and @optname refer to a NetLabel option.
+ * Helper for selinux_netlbl_socket_setsockopt().
+ */
+static inline int selinux_netlbl_option(int level, int optname)
+{
+       return (level == IPPROTO_IP && optname == IP_OPTIONS) ||
+               (level == IPPROTO_IPV6 && optname == IPV6_HOPOPTS);
+}
+
 /**
  * selinux_netlbl_socket_setsockopt - Do not allow users to remove a NetLabel
  * @sock: the socket
@@ -431,7 +449,7 @@ int selinux_netlbl_socket_setsockopt(struct socket *sock,
        struct sk_security_struct *sksec = sk->sk_security;
        struct netlbl_lsm_secattr secattr;
 
-       if (level == IPPROTO_IP && optname == IP_OPTIONS &&
+       if (selinux_netlbl_option(level, optname) &&
            (sksec->nlbl_state == NLBL_LABELED ||
             sksec->nlbl_state == NLBL_CONNLABELED)) {
                netlbl_secattr_init(&secattr);
index 1b1fd27de632692dc307e5a72cf65898f0e245b3..0765c5b053b52ce44819292acbef59319ffe8a6f 100644 (file)
@@ -1347,7 +1347,7 @@ static ssize_t sel_write_avc_cache_threshold(struct file *file,
 {
        char *page;
        ssize_t ret;
-       int new_value;
+       unsigned int new_value;
 
        ret = task_has_security(current, SECURITY__SETSECPARAM);
        if (ret)
index 57644b1dc42e1a38b5f1fd0d24ab75164b3bdbf1..894b6cdc11c5410591aaf46a3170eeea13dc444a 100644 (file)
@@ -165,7 +165,7 @@ int ebitmap_netlbl_import(struct ebitmap *ebmap,
                        e_iter = kzalloc(sizeof(*e_iter), GFP_ATOMIC);
                        if (e_iter == NULL)
                                goto netlbl_import_failure;
-                       e_iter->startbit = offset & ~(EBITMAP_SIZE - 1);
+                       e_iter->startbit = offset - (offset % EBITMAP_SIZE);
                        if (e_prev == NULL)
                                ebmap->node = e_iter;
                        else
index 89df64672b8974ab2fec0151ecdc1d35576f14a5..082b20c78363c8e604f7d3b490ca898b4e8c4730 100644 (file)
@@ -543,7 +543,7 @@ static void type_attribute_bounds_av(struct context *scontext,
                                     struct av_decision *avd)
 {
        struct context lo_scontext;
-       struct context lo_tcontext;
+       struct context lo_tcontext, *tcontextp = tcontext;
        struct av_decision lo_avd;
        struct type_datum *source;
        struct type_datum *target;
@@ -553,67 +553,41 @@ static void type_attribute_bounds_av(struct context *scontext,
                                    scontext->type - 1);
        BUG_ON(!source);
 
+       if (!source->bounds)
+               return;
+
        target = flex_array_get_ptr(policydb.type_val_to_struct_array,
                                    tcontext->type - 1);
        BUG_ON(!target);
 
-       if (source->bounds) {
-               memset(&lo_avd, 0, sizeof(lo_avd));
-
-               memcpy(&lo_scontext, scontext, sizeof(lo_scontext));
-               lo_scontext.type = source->bounds;
+       memset(&lo_avd, 0, sizeof(lo_avd));
 
-               context_struct_compute_av(&lo_scontext,
-                                         tcontext,
-                                         tclass,
-                                         &lo_avd,
-                                         NULL);
-               if ((lo_avd.allowed & avd->allowed) == avd->allowed)
-                       return;         /* no masked permission */
-               masked = ~lo_avd.allowed & avd->allowed;
-       }
+       memcpy(&lo_scontext, scontext, sizeof(lo_scontext));
+       lo_scontext.type = source->bounds;
 
        if (target->bounds) {
-               memset(&lo_avd, 0, sizeof(lo_avd));
-
                memcpy(&lo_tcontext, tcontext, sizeof(lo_tcontext));
                lo_tcontext.type = target->bounds;
-
-               context_struct_compute_av(scontext,
-                                         &lo_tcontext,
-                                         tclass,
-                                         &lo_avd,
-                                         NULL);
-               if ((lo_avd.allowed & avd->allowed) == avd->allowed)
-                       return;         /* no masked permission */
-               masked = ~lo_avd.allowed & avd->allowed;
+               tcontextp = &lo_tcontext;
        }
 
-       if (source->bounds && target->bounds) {
-               memset(&lo_avd, 0, sizeof(lo_avd));
-               /*
-                * lo_scontext and lo_tcontext are already
-                * set up.
-                */
+       context_struct_compute_av(&lo_scontext,
+                                 tcontextp,
+                                 tclass,
+                                 &lo_avd,
+                                 NULL);
 
-               context_struct_compute_av(&lo_scontext,
-                                         &lo_tcontext,
-                                         tclass,
-                                         &lo_avd,
-                                         NULL);
-               if ((lo_avd.allowed & avd->allowed) == avd->allowed)
-                       return;         /* no masked permission */
-               masked = ~lo_avd.allowed & avd->allowed;
-       }
+       masked = ~lo_avd.allowed & avd->allowed;
 
-       if (masked) {
-               /* mask violated permissions */
-               avd->allowed &= ~masked;
+       if (likely(!masked))
+               return;         /* no masked permission */
 
-               /* audit masked permissions */
-               security_dump_masked_av(scontext, tcontext,
-                                       tclass, masked, "bounds");
-       }
+       /* mask violated permissions */
+       avd->allowed &= ~masked;
+
+       /* audit masked permissions */
+       security_dump_masked_av(scontext, tcontext,
+                               tclass, masked, "bounds");
 }
 
 /*
index 6777295f4b2b75bd4edc385a60a09cdc4fdde0c0..722f3844286b37e90b14deb9ce22e5bba4244ae9 100644 (file)
@@ -3992,7 +3992,7 @@ access_check:
                rc = smk_bu_note("IPv4 delivery", skp, ssp->smk_in,
                                        MAY_WRITE, rc);
                if (rc != 0)
-                       netlbl_skbuff_err(skb, rc, 0);
+                       netlbl_skbuff_err(skb, sk->sk_family, rc, 0);
                break;
 #if IS_ENABLED(CONFIG_IPV6)
        case PF_INET6: