diff --git a/include/linux/audit.h b/include/linux/audit.h index c68781692838..bdd6f5de5fc4 100644 --- a/include/linux/audit.h +++ b/include/linux/audit.h @@ -115,6 +115,8 @@ #define AUDIT_MAC_IPSEC_ADDSPD 1413 /* Not used */ #define AUDIT_MAC_IPSEC_DELSPD 1414 /* Not used */ #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_FIRST_KERN_ANOM_MSG 1700 #define AUDIT_LAST_KERN_ANOM_MSG 1799 diff --git a/include/linux/selinux.h b/include/linux/selinux.h index 6080f73fc85f..8c2cc4c02526 100644 --- a/include/linux/selinux.h +++ b/include/linux/selinux.h @@ -120,16 +120,35 @@ void selinux_get_task_sid(struct task_struct *tsk, u32 *sid); int selinux_string_to_sid(char *str, u32 *sid); /** - * selinux_relabel_packet_permission - check permission to relabel a packet - * @sid: ID value to be applied to network packet (via SECMARK, most likely) + * selinux_secmark_relabel_packet_permission - secmark permission check + * @sid: SECMARK ID value to be applied to network packet * - * Returns 0 if the current task is allowed to label packets with the - * supplied security ID. Note that it is implicit that the packet is always - * being relabeled from the default unlabled value, and that the access - * control decision is made in the AVC. + * Returns 0 if the current task is allowed to set the SECMARK label of + * packets with the supplied security ID. Note that it is implicit that + * the packet is always being relabeled from the default unlabeled value, + * and that the access control decision is made in the AVC. */ -int selinux_relabel_packet_permission(u32 sid); +int selinux_secmark_relabel_packet_permission(u32 sid); +/** + * selinux_secmark_refcount_inc - increments the secmark use counter + * + * SELinux keeps track of the current SECMARK targets in use so it knows + * when to apply SECMARK label access checks to network packets. This + * function incements this reference count to indicate that a new SECMARK + * target has been configured. + */ +void selinux_secmark_refcount_inc(void); + +/** + * selinux_secmark_refcount_dec - decrements the secmark use counter + * + * SELinux keeps track of the current SECMARK targets in use so it knows + * when to apply SECMARK label access checks to network packets. This + * function decements this reference count to indicate that one of the + * existing SECMARK targets has been removed/flushed. + */ +void selinux_secmark_refcount_dec(void); #else static inline int selinux_audit_rule_init(u32 field, u32 op, @@ -184,11 +203,21 @@ static inline int selinux_string_to_sid(const char *str, u32 *sid) return 0; } -static inline int selinux_relabel_packet_permission(u32 sid) +static inline int selinux_secmark_relabel_packet_permission(u32 sid) { return 0; } +static inline void selinux_secmark_refcount_inc(void) +{ + return; +} + +static inline void selinux_secmark_refcount_dec(void) +{ + return; +} + #endif /* CONFIG_SECURITY_SELINUX */ #endif /* _LINUX_SELINUX_H */ diff --git a/include/net/netlabel.h b/include/net/netlabel.h index 2e5b2f6f9fa0..b3213c7c5309 100644 --- a/include/net/netlabel.h +++ b/include/net/netlabel.h @@ -67,7 +67,11 @@ * NetLabel NETLINK protocol */ -#define NETLBL_PROTO_VERSION 1 +/* NetLabel NETLINK protocol version + * 1: initial version + * 2: added static labels for unlabeled connections + */ +#define NETLBL_PROTO_VERSION 2 /* NetLabel NETLINK types/families */ #define NETLBL_NLTYPE_NONE 0 @@ -105,17 +109,49 @@ struct netlbl_dom_map; /* Domain mapping operations */ int netlbl_domhsh_remove(const char *domain, struct netlbl_audit *audit_info); -/* LSM security attributes */ +/* + * LSM security attributes + */ + +/** + * struct netlbl_lsm_cache - NetLabel LSM security attribute cache + * @refcount: atomic reference counter + * @free: LSM supplied function to free the cache data + * @data: LSM supplied cache data + * + * Description: + * This structure is provided for LSMs which wish to make use of the NetLabel + * caching mechanism to store LSM specific data/attributes in the NetLabel + * cache. If the LSM has to perform a lot of translation from the NetLabel + * security attributes into it's own internal representation then the cache + * mechanism can provide a way to eliminate some or all of that translation + * overhead on a cache hit. + * + */ struct netlbl_lsm_cache { atomic_t refcount; void (*free) (const void *data); void *data; }; -/* The catmap bitmap field MUST be a power of two in length and large + +/** + * struct netlbl_lsm_secattr_catmap - NetLabel LSM secattr category bitmap + * @startbit: the value of the lowest order bit in the bitmap + * @bitmap: the category bitmap + * @next: pointer to the next bitmap "node" or NULL + * + * Description: + * This structure is used to represent category bitmaps. Due to the large + * number of categories supported by most labeling protocols it is not + * practical to transfer a full bitmap internally so NetLabel adopts a sparse + * bitmap structure modeled after SELinux's ebitmap structure. + * The catmap bitmap field MUST be a power of two in length and large * enough to hold at least 240 bits. Special care (i.e. check the code!) * should be used when changing these values as the LSM implementation * probably has functions which rely on the sizes of these types to speed - * processing. */ + * processing. + * + */ #define NETLBL_CATMAP_MAPTYPE u64 #define NETLBL_CATMAP_MAPCNT 4 #define NETLBL_CATMAP_MAPSIZE (sizeof(NETLBL_CATMAP_MAPTYPE) * 8) @@ -127,22 +163,48 @@ struct netlbl_lsm_secattr_catmap { NETLBL_CATMAP_MAPTYPE bitmap[NETLBL_CATMAP_MAPCNT]; struct netlbl_lsm_secattr_catmap *next; }; + +/** + * struct netlbl_lsm_secattr - NetLabel LSM security attributes + * @flags: indicate which attributes are contained in this structure + * @type: indicate the NLTYPE of the attributes + * @domain: the NetLabel LSM domain + * @cache: NetLabel LSM specific cache + * @attr.mls: MLS sensitivity label + * @attr.mls.cat: MLS category bitmap + * @attr.mls.lvl: MLS sensitivity level + * @attr.secid: LSM specific secid token + * + * Description: + * This structure is used to pass security attributes between NetLabel and the + * LSM modules. The flags field is used to specify which fields within the + * struct are valid and valid values can be created by bitwise OR'ing the + * NETLBL_SECATTR_* defines. The domain field is typically set by the LSM to + * specify domain specific configuration settings and is not usually used by + * NetLabel itself when returning security attributes to the LSM. + * + */ #define NETLBL_SECATTR_NONE 0x00000000 #define NETLBL_SECATTR_DOMAIN 0x00000001 #define NETLBL_SECATTR_CACHE 0x00000002 #define NETLBL_SECATTR_MLS_LVL 0x00000004 #define NETLBL_SECATTR_MLS_CAT 0x00000008 +#define NETLBL_SECATTR_SECID 0x00000010 #define NETLBL_SECATTR_CACHEABLE (NETLBL_SECATTR_MLS_LVL | \ - NETLBL_SECATTR_MLS_CAT) + NETLBL_SECATTR_MLS_CAT | \ + NETLBL_SECATTR_SECID) struct netlbl_lsm_secattr { u32 flags; - + u32 type; char *domain; - - u32 mls_lvl; - struct netlbl_lsm_secattr_catmap *mls_cat; - struct netlbl_lsm_cache *cache; + union { + struct { + struct netlbl_lsm_secattr_catmap *cat; + u32 lvl; + } mls; + u32 secid; + } attr; }; /* @@ -231,10 +293,7 @@ static inline void netlbl_secattr_catmap_free( */ static inline void netlbl_secattr_init(struct netlbl_lsm_secattr *secattr) { - secattr->flags = 0; - secattr->domain = NULL; - secattr->mls_cat = NULL; - secattr->cache = NULL; + memset(secattr, 0, sizeof(*secattr)); } /** @@ -248,11 +307,11 @@ static inline void netlbl_secattr_init(struct netlbl_lsm_secattr *secattr) */ static inline void netlbl_secattr_destroy(struct netlbl_lsm_secattr *secattr) { - if (secattr->cache) - netlbl_secattr_cache_free(secattr->cache); kfree(secattr->domain); - if (secattr->mls_cat) - netlbl_secattr_catmap_free(secattr->mls_cat); + if (secattr->flags & NETLBL_SECATTR_CACHE) + netlbl_secattr_cache_free(secattr->cache); + if (secattr->flags & NETLBL_SECATTR_MLS_CAT) + netlbl_secattr_catmap_free(secattr->attr.mls.cat); } /** @@ -300,7 +359,7 @@ int netlbl_secattr_catmap_setrng(struct netlbl_lsm_secattr_catmap *catmap, gfp_t flags); /* - * LSM protocol operations + * LSM protocol operations (NetLabel LSM/kernel API) */ int netlbl_enabled(void); int netlbl_sock_setattr(struct sock *sk, @@ -308,6 +367,7 @@ int netlbl_sock_setattr(struct sock *sk, int netlbl_sock_getattr(struct sock *sk, struct netlbl_lsm_secattr *secattr); 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); @@ -360,6 +420,7 @@ static inline int netlbl_sock_getattr(struct sock *sk, return -ENOSYS; } static inline int netlbl_skbuff_getattr(const struct sk_buff *skb, + u16 family, struct netlbl_lsm_secattr *secattr) { return -ENOSYS; diff --git a/net/ipv4/cipso_ipv4.c b/net/ipv4/cipso_ipv4.c index d4dc4eb48d95..a2241060113b 100644 --- a/net/ipv4/cipso_ipv4.c +++ b/net/ipv4/cipso_ipv4.c @@ -348,6 +348,7 @@ static int cipso_v4_cache_check(const unsigned char *key, atomic_inc(&entry->lsm_data->refcount); secattr->cache = entry->lsm_data; secattr->flags |= NETLBL_SECATTR_CACHE; + secattr->type = NETLBL_NLTYPE_CIPSOV4; if (prev_entry == NULL) { spin_unlock_bh(&cipso_v4_cache[bkt].lock); return 0; @@ -865,7 +866,7 @@ static int cipso_v4_map_cat_rbm_hton(const struct cipso_v4_doi *doi_def, } for (;;) { - host_spot = netlbl_secattr_catmap_walk(secattr->mls_cat, + host_spot = netlbl_secattr_catmap_walk(secattr->attr.mls.cat, host_spot + 1); if (host_spot < 0) break; @@ -948,7 +949,7 @@ static int cipso_v4_map_cat_rbm_ntoh(const struct cipso_v4_doi *doi_def, return -EPERM; break; } - ret_val = netlbl_secattr_catmap_setbit(secattr->mls_cat, + ret_val = netlbl_secattr_catmap_setbit(secattr->attr.mls.cat, host_spot, GFP_ATOMIC); if (ret_val != 0) @@ -1014,7 +1015,8 @@ static int cipso_v4_map_cat_enum_hton(const struct cipso_v4_doi *doi_def, u32 cat_iter = 0; for (;;) { - cat = netlbl_secattr_catmap_walk(secattr->mls_cat, cat + 1); + cat = netlbl_secattr_catmap_walk(secattr->attr.mls.cat, + cat + 1); if (cat < 0) break; if ((cat_iter + 2) > net_cat_len) @@ -1049,7 +1051,7 @@ static int cipso_v4_map_cat_enum_ntoh(const struct cipso_v4_doi *doi_def, u32 iter; for (iter = 0; iter < net_cat_len; iter += 2) { - ret_val = netlbl_secattr_catmap_setbit(secattr->mls_cat, + ret_val = netlbl_secattr_catmap_setbit(secattr->attr.mls.cat, ntohs(get_unaligned((__be16 *)&net_cat[iter])), GFP_ATOMIC); if (ret_val != 0) @@ -1130,7 +1132,8 @@ static int cipso_v4_map_cat_rng_hton(const struct cipso_v4_doi *doi_def, return -ENOSPC; for (;;) { - iter = netlbl_secattr_catmap_walk(secattr->mls_cat, iter + 1); + iter = netlbl_secattr_catmap_walk(secattr->attr.mls.cat, + iter + 1); if (iter < 0) break; cat_size += (iter == 0 ? 0 : sizeof(u16)); @@ -1138,7 +1141,8 @@ static int cipso_v4_map_cat_rng_hton(const struct cipso_v4_doi *doi_def, return -ENOSPC; array[array_cnt++] = iter; - iter = netlbl_secattr_catmap_walk_rng(secattr->mls_cat, iter); + iter = netlbl_secattr_catmap_walk_rng(secattr->attr.mls.cat, + iter); if (iter < 0) return -EFAULT; cat_size += sizeof(u16); @@ -1191,7 +1195,7 @@ static int cipso_v4_map_cat_rng_ntoh(const struct cipso_v4_doi *doi_def, else cat_low = 0; - ret_val = netlbl_secattr_catmap_setrng(secattr->mls_cat, + ret_val = netlbl_secattr_catmap_setrng(secattr->attr.mls.cat, cat_low, cat_high, GFP_ATOMIC); @@ -1251,7 +1255,9 @@ static int cipso_v4_gentag_rbm(const struct cipso_v4_doi *doi_def, if ((secattr->flags & NETLBL_SECATTR_MLS_LVL) == 0) return -EPERM; - ret_val = cipso_v4_map_lvl_hton(doi_def, secattr->mls_lvl, &level); + ret_val = cipso_v4_map_lvl_hton(doi_def, + secattr->attr.mls.lvl, + &level); if (ret_val != 0) return ret_val; @@ -1303,12 +1309,13 @@ static int cipso_v4_parsetag_rbm(const struct cipso_v4_doi *doi_def, ret_val = cipso_v4_map_lvl_ntoh(doi_def, tag[3], &level); if (ret_val != 0) return ret_val; - secattr->mls_lvl = level; + secattr->attr.mls.lvl = level; secattr->flags |= NETLBL_SECATTR_MLS_LVL; if (tag_len > 4) { - secattr->mls_cat = netlbl_secattr_catmap_alloc(GFP_ATOMIC); - if (secattr->mls_cat == NULL) + secattr->attr.mls.cat = + netlbl_secattr_catmap_alloc(GFP_ATOMIC); + if (secattr->attr.mls.cat == NULL) return -ENOMEM; ret_val = cipso_v4_map_cat_rbm_ntoh(doi_def, @@ -1316,7 +1323,7 @@ static int cipso_v4_parsetag_rbm(const struct cipso_v4_doi *doi_def, tag_len - 4, secattr); if (ret_val != 0) { - netlbl_secattr_catmap_free(secattr->mls_cat); + netlbl_secattr_catmap_free(secattr->attr.mls.cat); return ret_val; } @@ -1350,7 +1357,9 @@ static int cipso_v4_gentag_enum(const struct cipso_v4_doi *doi_def, if (!(secattr->flags & NETLBL_SECATTR_MLS_LVL)) return -EPERM; - ret_val = cipso_v4_map_lvl_hton(doi_def, secattr->mls_lvl, &level); + ret_val = cipso_v4_map_lvl_hton(doi_def, + secattr->attr.mls.lvl, + &level); if (ret_val != 0) return ret_val; @@ -1396,12 +1405,13 @@ static int cipso_v4_parsetag_enum(const struct cipso_v4_doi *doi_def, ret_val = cipso_v4_map_lvl_ntoh(doi_def, tag[3], &level); if (ret_val != 0) return ret_val; - secattr->mls_lvl = level; + secattr->attr.mls.lvl = level; secattr->flags |= NETLBL_SECATTR_MLS_LVL; if (tag_len > 4) { - secattr->mls_cat = netlbl_secattr_catmap_alloc(GFP_ATOMIC); - if (secattr->mls_cat == NULL) + secattr->attr.mls.cat = + netlbl_secattr_catmap_alloc(GFP_ATOMIC); + if (secattr->attr.mls.cat == NULL) return -ENOMEM; ret_val = cipso_v4_map_cat_enum_ntoh(doi_def, @@ -1409,7 +1419,7 @@ static int cipso_v4_parsetag_enum(const struct cipso_v4_doi *doi_def, tag_len - 4, secattr); if (ret_val != 0) { - netlbl_secattr_catmap_free(secattr->mls_cat); + netlbl_secattr_catmap_free(secattr->attr.mls.cat); return ret_val; } @@ -1443,7 +1453,9 @@ static int cipso_v4_gentag_rng(const struct cipso_v4_doi *doi_def, if (!(secattr->flags & NETLBL_SECATTR_MLS_LVL)) return -EPERM; - ret_val = cipso_v4_map_lvl_hton(doi_def, secattr->mls_lvl, &level); + ret_val = cipso_v4_map_lvl_hton(doi_def, + secattr->attr.mls.lvl, + &level); if (ret_val != 0) return ret_val; @@ -1488,12 +1500,13 @@ static int cipso_v4_parsetag_rng(const struct cipso_v4_doi *doi_def, ret_val = cipso_v4_map_lvl_ntoh(doi_def, tag[3], &level); if (ret_val != 0) return ret_val; - secattr->mls_lvl = level; + secattr->attr.mls.lvl = level; secattr->flags |= NETLBL_SECATTR_MLS_LVL; if (tag_len > 4) { - secattr->mls_cat = netlbl_secattr_catmap_alloc(GFP_ATOMIC); - if (secattr->mls_cat == NULL) + secattr->attr.mls.cat = + netlbl_secattr_catmap_alloc(GFP_ATOMIC); + if (secattr->attr.mls.cat == NULL) return -ENOMEM; ret_val = cipso_v4_map_cat_rng_ntoh(doi_def, @@ -1501,7 +1514,7 @@ static int cipso_v4_parsetag_rng(const struct cipso_v4_doi *doi_def, tag_len - 4, secattr); if (ret_val != 0) { - netlbl_secattr_catmap_free(secattr->mls_cat); + netlbl_secattr_catmap_free(secattr->attr.mls.cat); return ret_val; } @@ -1850,6 +1863,8 @@ static int cipso_v4_getattr(const unsigned char *cipso, ret_val = cipso_v4_parsetag_rng(doi_def, &cipso[6], secattr); break; } + if (ret_val == 0) + secattr->type = NETLBL_NLTYPE_CIPSOV4; getattr_return: rcu_read_unlock(); diff --git a/net/netfilter/xt_SECMARK.c b/net/netfilter/xt_SECMARK.c index b11b3ecbb39d..7708e2084ce2 100644 --- a/net/netfilter/xt_SECMARK.c +++ b/net/netfilter/xt_SECMARK.c @@ -72,12 +72,13 @@ static bool checkentry_selinux(struct xt_secmark_target_info *info) return false; } - err = selinux_relabel_packet_permission(sel->selsid); + err = selinux_secmark_relabel_packet_permission(sel->selsid); if (err) { printk(KERN_INFO PFX "unable to obtain relabeling permission\n"); return false; } + selinux_secmark_refcount_inc(); return true; } @@ -110,11 +111,20 @@ secmark_tg_check(const char *tablename, const void *entry, return true; } +void secmark_tg_destroy(const struct xt_target *target, void *targinfo) +{ + switch (mode) { + case SECMARK_MODE_SEL: + selinux_secmark_refcount_dec(); + } +} + static struct xt_target secmark_tg_reg[] __read_mostly = { { .name = "SECMARK", .family = AF_INET, .checkentry = secmark_tg_check, + .destroy = secmark_tg_destroy, .target = secmark_tg, .targetsize = sizeof(struct xt_secmark_target_info), .table = "mangle", @@ -124,6 +134,7 @@ static struct xt_target secmark_tg_reg[] __read_mostly = { .name = "SECMARK", .family = AF_INET6, .checkentry = secmark_tg_check, + .destroy = secmark_tg_destroy, .target = secmark_tg, .targetsize = sizeof(struct xt_secmark_target_info), .table = "mangle", diff --git a/net/netlabel/netlabel_cipso_v4.c b/net/netlabel/netlabel_cipso_v4.c index ba0ca8d3f77d..becf91a952ae 100644 --- a/net/netlabel/netlabel_cipso_v4.c +++ b/net/netlabel/netlabel_cipso_v4.c @@ -38,6 +38,7 @@ #include #include #include +#include #include "netlabel_user.h" #include "netlabel_cipso_v4.h" @@ -421,7 +422,7 @@ static int netlbl_cipsov4_add(struct sk_buff *skb, struct genl_info *info) break; } if (ret_val == 0) - netlbl_mgmt_protocount_inc(); + atomic_inc(&netlabel_mgmt_protocount); audit_buf = netlbl_audit_start_common(AUDIT_MAC_CIPSOV4_ADD, &audit_info); @@ -698,7 +699,7 @@ static int netlbl_cipsov4_remove(struct sk_buff *skb, struct genl_info *info) &audit_info, netlbl_cipsov4_doi_free); if (ret_val == 0) - netlbl_mgmt_protocount_dec(); + atomic_dec(&netlabel_mgmt_protocount); audit_buf = netlbl_audit_start_common(AUDIT_MAC_CIPSOV4_DEL, &audit_info); diff --git a/net/netlabel/netlabel_domainhash.c b/net/netlabel/netlabel_domainhash.c index b3675bd7db33..9a8ea0195c4f 100644 --- a/net/netlabel/netlabel_domainhash.c +++ b/net/netlabel/netlabel_domainhash.c @@ -54,9 +54,6 @@ struct netlbl_domhsh_tbl { * hash table should be okay */ static DEFINE_SPINLOCK(netlbl_domhsh_lock); static struct netlbl_domhsh_tbl *netlbl_domhsh = NULL; - -/* Default domain mapping */ -static DEFINE_SPINLOCK(netlbl_domhsh_def_lock); static struct netlbl_dom_map *netlbl_domhsh_def = NULL; /* @@ -109,17 +106,14 @@ static u32 netlbl_domhsh_hash(const char *key) /** * netlbl_domhsh_search - Search for a domain entry * @domain: the domain - * @def: return default if no match is found * * Description: * Searches the domain hash table and returns a pointer to the hash table - * entry if found, otherwise NULL is returned. If @def is non-zero and a - * match is not found in the domain hash table the default mapping is returned - * if it exists. The caller is responsibile for the rcu hash table locks - * (i.e. the caller much call rcu_read_[un]lock()). + * entry if found, otherwise NULL is returned. The caller is responsibile for + * the rcu hash table locks (i.e. the caller much call rcu_read_[un]lock()). * */ -static struct netlbl_dom_map *netlbl_domhsh_search(const char *domain, u32 def) +static struct netlbl_dom_map *netlbl_domhsh_search(const char *domain) { u32 bkt; struct netlbl_dom_map *iter; @@ -133,10 +127,31 @@ static struct netlbl_dom_map *netlbl_domhsh_search(const char *domain, u32 def) return iter; } - if (def != 0) { - iter = rcu_dereference(netlbl_domhsh_def); - if (iter != NULL && iter->valid) - return iter; + return NULL; +} + +/** + * netlbl_domhsh_search_def - Search for a domain entry + * @domain: the domain + * @def: return default if no match is found + * + * 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 responsibile for the rcu hash table locks + * (i.e. the caller much call rcu_read_[un]lock()). + * + */ +static struct netlbl_dom_map *netlbl_domhsh_search_def(const char *domain) +{ + struct netlbl_dom_map *entry; + + entry = netlbl_domhsh_search(domain); + if (entry == NULL) { + entry = rcu_dereference(netlbl_domhsh_def); + if (entry != NULL && entry->valid) + return entry; } return NULL; @@ -221,24 +236,22 @@ int netlbl_domhsh_add(struct netlbl_dom_map *entry, INIT_RCU_HEAD(&entry->rcu); rcu_read_lock(); + spin_lock(&netlbl_domhsh_lock); if (entry->domain != NULL) { bkt = netlbl_domhsh_hash(entry->domain); - spin_lock(&netlbl_domhsh_lock); - if (netlbl_domhsh_search(entry->domain, 0) == NULL) + if (netlbl_domhsh_search(entry->domain) == NULL) list_add_tail_rcu(&entry->list, &rcu_dereference(netlbl_domhsh)->tbl[bkt]); else ret_val = -EEXIST; - spin_unlock(&netlbl_domhsh_lock); } else { INIT_LIST_HEAD(&entry->list); - spin_lock(&netlbl_domhsh_def_lock); if (rcu_dereference(netlbl_domhsh_def) == NULL) rcu_assign_pointer(netlbl_domhsh_def, entry); else ret_val = -EEXIST; - spin_unlock(&netlbl_domhsh_def_lock); } + spin_unlock(&netlbl_domhsh_lock); audit_buf = netlbl_audit_start_common(AUDIT_MAC_MAP_ADD, audit_info); if (audit_buf != NULL) { audit_log_format(audit_buf, @@ -307,7 +320,10 @@ int netlbl_domhsh_remove(const char *domain, struct netlbl_audit *audit_info) struct audit_buffer *audit_buf; rcu_read_lock(); - entry = netlbl_domhsh_search(domain, (domain != NULL ? 0 : 1)); + if (domain) + entry = netlbl_domhsh_search(domain); + else + entry = netlbl_domhsh_search_def(domain); if (entry == NULL) goto remove_return; switch (entry->type) { @@ -316,23 +332,16 @@ int netlbl_domhsh_remove(const char *domain, struct netlbl_audit *audit_info) entry->domain); break; } - if (entry != rcu_dereference(netlbl_domhsh_def)) { - spin_lock(&netlbl_domhsh_lock); - if (entry->valid) { - entry->valid = 0; + spin_lock(&netlbl_domhsh_lock); + if (entry->valid) { + entry->valid = 0; + if (entry != rcu_dereference(netlbl_domhsh_def)) list_del_rcu(&entry->list); - ret_val = 0; - } - spin_unlock(&netlbl_domhsh_lock); - } else { - spin_lock(&netlbl_domhsh_def_lock); - if (entry->valid) { - entry->valid = 0; + else rcu_assign_pointer(netlbl_domhsh_def, NULL); - ret_val = 0; - } - spin_unlock(&netlbl_domhsh_def_lock); + ret_val = 0; } + spin_unlock(&netlbl_domhsh_lock); audit_buf = netlbl_audit_start_common(AUDIT_MAC_MAP_DEL, audit_info); if (audit_buf != NULL) { @@ -377,7 +386,7 @@ int netlbl_domhsh_remove_default(struct netlbl_audit *audit_info) */ struct netlbl_dom_map *netlbl_domhsh_getentry(const char *domain) { - return netlbl_domhsh_search(domain, 1); + return netlbl_domhsh_search_def(domain); } /** diff --git a/net/netlabel/netlabel_kapi.c b/net/netlabel/netlabel_kapi.c index 4f50949722a9..c69e3e1f05c3 100644 --- a/net/netlabel/netlabel_kapi.c +++ b/net/netlabel/netlabel_kapi.c @@ -34,6 +34,7 @@ #include #include #include +#include #include "netlabel_domainhash.h" #include "netlabel_unlabeled.h" @@ -262,7 +263,7 @@ int netlbl_enabled(void) /* At some point we probably want to expose this mechanism to the user * as well so that admins can toggle NetLabel regardless of the * configuration */ - return (netlbl_mgmt_protocount_value() > 0 ? 1 : 0); + return (atomic_read(&netlabel_mgmt_protocount) > 0); } /** @@ -311,7 +312,7 @@ int netlbl_sock_setattr(struct sock *sk, * @secattr: the security attributes * * Description: - * Examines the given sock to see any NetLabel style labeling has been + * Examines the given sock to see if any NetLabel style labeling has been * applied to the sock, if so it parses the socket label and returns the * security attributes in @secattr. Returns zero on success, negative values * on failure. @@ -319,18 +320,13 @@ int netlbl_sock_setattr(struct sock *sk, */ int netlbl_sock_getattr(struct sock *sk, struct netlbl_lsm_secattr *secattr) { - int ret_val; - - ret_val = cipso_v4_sock_getattr(sk, secattr); - if (ret_val == 0) - return 0; - - return netlbl_unlabel_getattr(secattr); + return cipso_v4_sock_getattr(sk, secattr); } /** * netlbl_skbuff_getattr - Determine the security attributes of a packet * @skb: the packet + * @family: protocol family * @secattr: the security attributes * * Description: @@ -341,13 +337,14 @@ int netlbl_sock_getattr(struct sock *sk, struct netlbl_lsm_secattr *secattr) * */ int netlbl_skbuff_getattr(const struct sk_buff *skb, + u16 family, struct netlbl_lsm_secattr *secattr) { if (CIPSO_V4_OPTEXIST(skb) && cipso_v4_skbuff_getattr(skb, secattr) == 0) return 0; - return netlbl_unlabel_getattr(secattr); + return netlbl_unlabel_getattr(skb, family, secattr); } /** @@ -431,6 +428,10 @@ static int __init netlbl_init(void) if (ret_val != 0) goto init_failure; + ret_val = netlbl_unlabel_init(NETLBL_UNLHSH_BITSIZE); + if (ret_val != 0) + goto init_failure; + ret_val = netlbl_netlink_init(); if (ret_val != 0) goto init_failure; diff --git a/net/netlabel/netlabel_mgmt.c b/net/netlabel/netlabel_mgmt.c index 9c41464d58d1..e2258dc3c845 100644 --- a/net/netlabel/netlabel_mgmt.c +++ b/net/netlabel/netlabel_mgmt.c @@ -37,14 +37,14 @@ #include #include #include +#include #include "netlabel_domainhash.h" #include "netlabel_user.h" #include "netlabel_mgmt.h" -/* NetLabel configured protocol count */ -static DEFINE_SPINLOCK(netlabel_mgmt_protocount_lock); -static u32 netlabel_mgmt_protocount = 0; +/* NetLabel configured protocol counter */ +atomic_t netlabel_mgmt_protocount = ATOMIC_INIT(0); /* Argument struct for netlbl_domhsh_walk() */ struct netlbl_domhsh_walk_arg { @@ -70,63 +70,6 @@ static const struct nla_policy netlbl_mgmt_genl_policy[NLBL_MGMT_A_MAX + 1] = { [NLBL_MGMT_A_CV4DOI] = { .type = NLA_U32 }, }; -/* - * NetLabel Misc Management Functions - */ - -/** - * netlbl_mgmt_protocount_inc - Increment the configured labeled protocol count - * - * Description: - * Increment the number of labeled protocol configurations in the current - * NetLabel configuration. Keep track of this for use in determining if - * NetLabel label enforcement should be active/enabled or not in the LSM. - * - */ -void netlbl_mgmt_protocount_inc(void) -{ - spin_lock(&netlabel_mgmt_protocount_lock); - netlabel_mgmt_protocount++; - spin_unlock(&netlabel_mgmt_protocount_lock); -} - -/** - * netlbl_mgmt_protocount_dec - Decrement the configured labeled protocol count - * - * Description: - * Decrement the number of labeled protocol configurations in the current - * NetLabel configuration. Keep track of this for use in determining if - * NetLabel label enforcement should be active/enabled or not in the LSM. - * - */ -void netlbl_mgmt_protocount_dec(void) -{ - spin_lock(&netlabel_mgmt_protocount_lock); - if (netlabel_mgmt_protocount > 0) - netlabel_mgmt_protocount--; - spin_unlock(&netlabel_mgmt_protocount_lock); -} - -/** - * netlbl_mgmt_protocount_value - Return the number of configured protocols - * - * Description: - * Return the number of labeled protocols in the current NetLabel - * configuration. This value is useful in determining if NetLabel label - * enforcement should be active/enabled or not in the LSM. - * - */ -u32 netlbl_mgmt_protocount_value(void) -{ - u32 val; - - rcu_read_lock(); - val = netlabel_mgmt_protocount; - rcu_read_unlock(); - - return val; -} - /* * NetLabel Command Handlers */ diff --git a/net/netlabel/netlabel_mgmt.h b/net/netlabel/netlabel_mgmt.h index ccb2b3923591..a43bff169d6b 100644 --- a/net/netlabel/netlabel_mgmt.h +++ b/net/netlabel/netlabel_mgmt.h @@ -32,6 +32,7 @@ #define _NETLABEL_MGMT_H #include +#include /* * The following NetLabel payloads are supported by the management interface. @@ -168,9 +169,7 @@ enum { /* NetLabel protocol functions */ int netlbl_mgmt_genl_init(void); -/* NetLabel misc management functions */ -void netlbl_mgmt_protocount_inc(void); -void netlbl_mgmt_protocount_dec(void); -u32 netlbl_mgmt_protocount_value(void); +/* NetLabel configured protocol reference counter */ +extern atomic_t netlabel_mgmt_protocount; #endif diff --git a/net/netlabel/netlabel_unlabeled.c b/net/netlabel/netlabel_unlabeled.c index 348292450deb..42e81fd8cc49 100644 --- a/net/netlabel/netlabel_unlabeled.c +++ b/net/netlabel/netlabel_unlabeled.c @@ -10,7 +10,7 @@ */ /* - * (c) Copyright Hewlett-Packard Development Company, L.P., 2006 + * (c) Copyright Hewlett-Packard Development Company, L.P., 2006 - 2007 * * 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 @@ -36,22 +36,92 @@ #include #include #include +#include +#include +#include +#include +#include +#include +#include #include #include #include - +#include +#include +#include #include #include +#include #include "netlabel_user.h" #include "netlabel_domainhash.h" #include "netlabel_unlabeled.h" +#include "netlabel_mgmt.h" + +/* NOTE: at present we always use init's network namespace since we don't + * presently support different namespaces even though the majority of + * the functions in this file are "namespace safe" */ + +/* The unlabeled connection hash table which we use to map network interfaces + * and addresses of unlabeled packets to a user specified secid value for the + * LSM. The hash table is used to lookup the network interface entry + * (struct netlbl_unlhsh_iface) and then the interface entry is used to + * lookup an IP address match from an ordered list. If a network interface + * match can not be found in the hash table then the default entry + * (netlbl_unlhsh_def) is used. The IP address entry list + * (struct netlbl_unlhsh_addr) is ordered such that the entries with a + * larger netmask come first. + */ +struct netlbl_unlhsh_tbl { + struct list_head *tbl; + u32 size; +}; +struct netlbl_unlhsh_addr4 { + __be32 addr; + __be32 mask; + u32 secid; + + u32 valid; + struct list_head list; + struct rcu_head rcu; +}; +struct netlbl_unlhsh_addr6 { + struct in6_addr addr; + struct in6_addr mask; + u32 secid; + + u32 valid; + struct list_head list; + struct rcu_head rcu; +}; +struct netlbl_unlhsh_iface { + int ifindex; + struct list_head addr4_list; + struct list_head addr6_list; + + u32 valid; + struct list_head list; + struct rcu_head rcu; +}; + +/* Argument struct for netlbl_unlhsh_walk() */ +struct netlbl_unlhsh_walk_arg { + struct netlink_callback *nl_cb; + struct sk_buff *skb; + u32 seq; +}; + +/* Unlabeled connection hash table */ +/* updates should be so rare that having one spinlock for the entire + * hash table should be okay */ +static DEFINE_SPINLOCK(netlbl_unlhsh_lock); +static struct netlbl_unlhsh_tbl *netlbl_unlhsh = NULL; +static struct netlbl_unlhsh_iface *netlbl_unlhsh_def = NULL; /* Accept unlabeled packets flag */ -static DEFINE_SPINLOCK(netlabel_unlabel_acceptflg_lock); static u8 netlabel_unlabel_acceptflg = 0; -/* NetLabel Generic NETLINK CIPSOv4 family */ +/* NetLabel Generic NETLINK unlabeled family */ static struct genl_family netlbl_unlabel_gnl_family = { .id = GENL_ID_GENERATE, .hdrsize = 0, @@ -63,12 +133,842 @@ static struct genl_family netlbl_unlabel_gnl_family = { /* NetLabel Netlink attribute policy */ static const struct nla_policy netlbl_unlabel_genl_policy[NLBL_UNLABEL_A_MAX + 1] = { [NLBL_UNLABEL_A_ACPTFLG] = { .type = NLA_U8 }, + [NLBL_UNLABEL_A_IPV6ADDR] = { .type = NLA_BINARY, + .len = sizeof(struct in6_addr) }, + [NLBL_UNLABEL_A_IPV6MASK] = { .type = NLA_BINARY, + .len = sizeof(struct in6_addr) }, + [NLBL_UNLABEL_A_IPV4ADDR] = { .type = NLA_BINARY, + .len = sizeof(struct in_addr) }, + [NLBL_UNLABEL_A_IPV4MASK] = { .type = NLA_BINARY, + .len = sizeof(struct in_addr) }, + [NLBL_UNLABEL_A_IFACE] = { .type = NLA_NUL_STRING, + .len = IFNAMSIZ - 1 }, + [NLBL_UNLABEL_A_SECCTX] = { .type = NLA_BINARY } }; /* - * Helper Functions + * Audit Helper Functions */ +/** + * netlbl_unlabel_audit_addr4 - Audit an IPv4 address + * @audit_buf: audit buffer + * @dev: network interface + * @addr: IP address + * @mask: IP address mask + * + * Description: + * Write the IPv4 address and address mask, if necessary, to @audit_buf. + * + */ +static void netlbl_unlabel_audit_addr4(struct audit_buffer *audit_buf, + const char *dev, + __be32 addr, __be32 mask) +{ + u32 mask_val = ntohl(mask); + + if (dev != NULL) + audit_log_format(audit_buf, " netif=%s", dev); + audit_log_format(audit_buf, " src=" NIPQUAD_FMT, NIPQUAD(addr)); + if (mask_val != 0xffffffff) { + u32 mask_len = 0; + while (mask_val > 0) { + mask_val <<= 1; + mask_len++; + } + audit_log_format(audit_buf, " src_prefixlen=%d", mask_len); + } +} + +/** + * netlbl_unlabel_audit_addr6 - Audit an IPv6 address + * @audit_buf: audit buffer + * @dev: network interface + * @addr: IP address + * @mask: IP address mask + * + * Description: + * Write the IPv6 address and address mask, if necessary, to @audit_buf. + * + */ +static void netlbl_unlabel_audit_addr6(struct audit_buffer *audit_buf, + const char *dev, + const struct in6_addr *addr, + const struct in6_addr *mask) +{ + if (dev != NULL) + audit_log_format(audit_buf, " netif=%s", dev); + audit_log_format(audit_buf, " src=" NIP6_FMT, NIP6(*addr)); + if (ntohl(mask->s6_addr32[3]) != 0xffffffff) { + u32 mask_len = 0; + u32 mask_val; + int iter = -1; + while (ntohl(mask->s6_addr32[++iter]) == 0xffffffff) + mask_len += 32; + mask_val = ntohl(mask->s6_addr32[iter]); + while (mask_val > 0) { + mask_val <<= 1; + mask_len++; + } + audit_log_format(audit_buf, " src_prefixlen=%d", mask_len); + } +} + +/* + * Unlabeled Connection Hash Table Functions + */ + +/** + * netlbl_unlhsh_free_addr4 - Frees an IPv4 address entry from the hash table + * @entry: the entry's RCU field + * + * Description: + * This function is designed to be used as a callback to the call_rcu() + * function so that memory allocated to a hash table address entry can be + * released safely. + * + */ +static void netlbl_unlhsh_free_addr4(struct rcu_head *entry) +{ + struct netlbl_unlhsh_addr4 *ptr; + + ptr = container_of(entry, struct netlbl_unlhsh_addr4, rcu); + kfree(ptr); +} + +#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE) +/** + * netlbl_unlhsh_free_addr6 - Frees an IPv6 address entry from the hash table + * @entry: the entry's RCU field + * + * Description: + * This function is designed to be used as a callback to the call_rcu() + * function so that memory allocated to a hash table address entry can be + * released safely. + * + */ +static void netlbl_unlhsh_free_addr6(struct rcu_head *entry) +{ + struct netlbl_unlhsh_addr6 *ptr; + + ptr = container_of(entry, struct netlbl_unlhsh_addr6, rcu); + kfree(ptr); +} +#endif /* IPv6 */ + +/** + * netlbl_unlhsh_free_iface - Frees an interface entry from the hash table + * @entry: the entry's RCU field + * + * Description: + * This function is designed to be used as a callback to the call_rcu() + * function so that memory allocated to a hash table interface entry can be + * released safely. It is important to note that this function does not free + * the IPv4 and IPv6 address lists contained as part of an interface entry. It + * is up to the rest of the code to make sure an interface entry is only freed + * once it's address lists are empty. + * + */ +static void netlbl_unlhsh_free_iface(struct rcu_head *entry) +{ + struct netlbl_unlhsh_iface *iface; + struct netlbl_unlhsh_addr4 *iter4; + struct netlbl_unlhsh_addr4 *tmp4; + struct netlbl_unlhsh_addr6 *iter6; + struct netlbl_unlhsh_addr6 *tmp6; + + iface = container_of(entry, struct netlbl_unlhsh_iface, rcu); + + /* no need for locks here since we are the only one with access to this + * structure */ + + list_for_each_entry_safe(iter4, tmp4, &iface->addr4_list, list) + if (iter4->valid) { + list_del_rcu(&iter4->list); + kfree(iter4); + } + list_for_each_entry_safe(iter6, tmp6, &iface->addr6_list, list) + if (iter6->valid) { + list_del_rcu(&iter6->list); + kfree(iter6); + } + kfree(iface); +} + +/** + * netlbl_unlhsh_hash - Hashing function for the hash table + * @ifindex: the network interface/device to hash + * + * Description: + * This is the hashing function for the unlabeled hash table, it returns the + * bucket number for the given device/interface. The caller is responsible for + * calling the rcu_read_[un]lock() functions. + * + */ +static u32 netlbl_unlhsh_hash(int ifindex) +{ + /* this is taken _almost_ directly from + * security/selinux/netif.c:sel_netif_hasfn() as they do pretty much + * the same thing */ + return ifindex & (rcu_dereference(netlbl_unlhsh)->size - 1); +} + +/** + * netlbl_unlhsh_search_addr4 - Search for a matching IPv4 address entry + * @addr: IPv4 address + * @iface: the network interface entry + * + * Description: + * Searches the IPv4 address list of the network interface specified by @iface. + * If a matching address entry is found it is returned, otherwise NULL is + * returned. The caller is responsible for calling the rcu_read_[un]lock() + * functions. + * + */ +static struct netlbl_unlhsh_addr4 *netlbl_unlhsh_search_addr4( + __be32 addr, + const struct netlbl_unlhsh_iface *iface) +{ + struct netlbl_unlhsh_addr4 *iter; + + list_for_each_entry_rcu(iter, &iface->addr4_list, list) + if (iter->valid && (addr & iter->mask) == iter->addr) + return iter; + + return NULL; +} + +#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE) +/** + * netlbl_unlhsh_search_addr6 - Search for a matching IPv6 address entry + * @addr: IPv6 address + * @iface: the network interface entry + * + * Description: + * Searches the IPv6 address list of the network interface specified by @iface. + * If a matching address entry is found it is returned, otherwise NULL is + * returned. The caller is responsible for calling the rcu_read_[un]lock() + * functions. + * + */ +static struct netlbl_unlhsh_addr6 *netlbl_unlhsh_search_addr6( + const struct in6_addr *addr, + const struct netlbl_unlhsh_iface *iface) +{ + struct netlbl_unlhsh_addr6 *iter; + + list_for_each_entry_rcu(iter, &iface->addr6_list, list) + if (iter->valid && + ipv6_masked_addr_cmp(&iter->addr, &iter->mask, addr) == 0) + return iter; + + return NULL; +} +#endif /* IPv6 */ + +/** + * netlbl_unlhsh_search_iface - Search for a matching interface entry + * @ifindex: the network interface + * + * Description: + * Searches the unlabeled connection hash table and returns a pointer to the + * interface entry which matches @ifindex, otherwise NULL is returned. The + * caller is responsible for calling the rcu_read_[un]lock() functions. + * + */ +static struct netlbl_unlhsh_iface *netlbl_unlhsh_search_iface(int ifindex) +{ + u32 bkt; + struct netlbl_unlhsh_iface *iter; + + bkt = netlbl_unlhsh_hash(ifindex); + list_for_each_entry_rcu(iter, + &rcu_dereference(netlbl_unlhsh)->tbl[bkt], + list) + if (iter->valid && iter->ifindex == ifindex) + return iter; + + return NULL; +} + +/** + * netlbl_unlhsh_search_iface_def - Search for a matching interface entry + * @ifindex: the network interface + * + * Description: + * Searches the unlabeled connection hash table and returns a pointer to the + * interface entry which matches @ifindex. If an exact match can not be found + * and there is a valid default entry, the default entry is returned, otherwise + * NULL is returned. The caller is responsible for calling the + * rcu_read_[un]lock() functions. + * + */ +static struct netlbl_unlhsh_iface *netlbl_unlhsh_search_iface_def(int ifindex) +{ + struct netlbl_unlhsh_iface *entry; + + entry = netlbl_unlhsh_search_iface(ifindex); + if (entry != NULL) + return entry; + + entry = rcu_dereference(netlbl_unlhsh_def); + if (entry != NULL && entry->valid) + return entry; + + return NULL; +} + +/** + * netlbl_unlhsh_add_addr4 - Add a new IPv4 address entry to the hash table + * @iface: the associated interface entry + * @addr: IPv4 address in network byte order + * @mask: IPv4 address mask in network byte order + * @secid: LSM secid value for entry + * + * Description: + * Add a new address entry into the unlabeled connection hash table using the + * interface entry specified by @iface. On success zero is returned, otherwise + * a negative value is returned. The caller is responsible for calling the + * rcu_read_[un]lock() functions. + * + */ +static int netlbl_unlhsh_add_addr4(struct netlbl_unlhsh_iface *iface, + const struct in_addr *addr, + const struct in_addr *mask, + u32 secid) +{ + struct netlbl_unlhsh_addr4 *entry; + struct netlbl_unlhsh_addr4 *iter; + + entry = kzalloc(sizeof(*entry), GFP_ATOMIC); + if (entry == NULL) + return -ENOMEM; + + entry->addr = addr->s_addr & mask->s_addr; + entry->mask = mask->s_addr; + entry->secid = secid; + entry->valid = 1; + INIT_RCU_HEAD(&entry->rcu); + + spin_lock(&netlbl_unlhsh_lock); + iter = netlbl_unlhsh_search_addr4(entry->addr, iface); + if (iter != NULL && + iter->addr == addr->s_addr && iter->mask == mask->s_addr) { + spin_unlock(&netlbl_unlhsh_lock); + kfree(entry); + return -EEXIST; + } + /* in order to speed up address searches through the list (the common + * case) we need to keep the list in order based on the size of the + * address mask such that the entry with the widest mask (smallest + * numerical value) appears first in the list */ + list_for_each_entry_rcu(iter, &iface->addr4_list, list) + if (iter->valid && + ntohl(entry->mask) > ntohl(iter->mask)) { + __list_add_rcu(&entry->list, + iter->list.prev, + &iter->list); + spin_unlock(&netlbl_unlhsh_lock); + return 0; + } + list_add_tail_rcu(&entry->list, &iface->addr4_list); + spin_unlock(&netlbl_unlhsh_lock); + return 0; +} + +#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE) +/** + * netlbl_unlhsh_add_addr6 - Add a new IPv6 address entry to the hash table + * @iface: the associated interface entry + * @addr: IPv6 address in network byte order + * @mask: IPv6 address mask in network byte order + * @secid: LSM secid value for entry + * + * Description: + * Add a new address entry into the unlabeled connection hash table using the + * interface entry specified by @iface. On success zero is returned, otherwise + * a negative value is returned. The caller is responsible for calling the + * rcu_read_[un]lock() functions. + * + */ +static int netlbl_unlhsh_add_addr6(struct netlbl_unlhsh_iface *iface, + const struct in6_addr *addr, + const struct in6_addr *mask, + u32 secid) +{ + struct netlbl_unlhsh_addr6 *entry; + struct netlbl_unlhsh_addr6 *iter; + + entry = kzalloc(sizeof(*entry), GFP_ATOMIC); + if (entry == NULL) + return -ENOMEM; + + ipv6_addr_copy(&entry->addr, addr); + entry->addr.s6_addr32[0] &= mask->s6_addr32[0]; + entry->addr.s6_addr32[1] &= mask->s6_addr32[1]; + entry->addr.s6_addr32[2] &= mask->s6_addr32[2]; + entry->addr.s6_addr32[3] &= mask->s6_addr32[3]; + ipv6_addr_copy(&entry->mask, mask); + entry->secid = secid; + entry->valid = 1; + INIT_RCU_HEAD(&entry->rcu); + + spin_lock(&netlbl_unlhsh_lock); + iter = netlbl_unlhsh_search_addr6(&entry->addr, iface); + if (iter != NULL && + (ipv6_addr_equal(&iter->addr, addr) && + ipv6_addr_equal(&iter->mask, mask))) { + spin_unlock(&netlbl_unlhsh_lock); + kfree(entry); + return -EEXIST; + } + /* in order to speed up address searches through the list (the common + * case) we need to keep the list in order based on the size of the + * address mask such that the entry with the widest mask (smallest + * numerical value) appears first in the list */ + list_for_each_entry_rcu(iter, &iface->addr6_list, list) + if (iter->valid && + ipv6_addr_cmp(&entry->mask, &iter->mask) > 0) { + __list_add_rcu(&entry->list, + iter->list.prev, + &iter->list); + spin_unlock(&netlbl_unlhsh_lock); + return 0; + } + list_add_tail_rcu(&entry->list, &iface->addr6_list); + spin_unlock(&netlbl_unlhsh_lock); + return 0; +} +#endif /* IPv6 */ + +/** + * netlbl_unlhsh_add_iface - Adds a new interface entry to the hash table + * @ifindex: network interface + * + * Description: + * Add a new, empty, interface entry into the unlabeled connection hash table. + * On success a pointer to the new interface entry is returned, on failure NULL + * is returned. The caller is responsible for calling the rcu_read_[un]lock() + * functions. + * + */ +static struct netlbl_unlhsh_iface *netlbl_unlhsh_add_iface(int ifindex) +{ + u32 bkt; + struct netlbl_unlhsh_iface *iface; + + iface = kzalloc(sizeof(*iface), GFP_ATOMIC); + if (iface == NULL) + return NULL; + + iface->ifindex = ifindex; + INIT_LIST_HEAD(&iface->addr4_list); + INIT_LIST_HEAD(&iface->addr6_list); + iface->valid = 1; + INIT_RCU_HEAD(&iface->rcu); + + spin_lock(&netlbl_unlhsh_lock); + if (ifindex > 0) { + bkt = netlbl_unlhsh_hash(ifindex); + if (netlbl_unlhsh_search_iface(ifindex) != NULL) + goto add_iface_failure; + list_add_tail_rcu(&iface->list, + &rcu_dereference(netlbl_unlhsh)->tbl[bkt]); + } else { + INIT_LIST_HEAD(&iface->list); + if (rcu_dereference(netlbl_unlhsh_def) != NULL) + goto add_iface_failure; + rcu_assign_pointer(netlbl_unlhsh_def, iface); + } + spin_unlock(&netlbl_unlhsh_lock); + + return iface; + +add_iface_failure: + spin_unlock(&netlbl_unlhsh_lock); + kfree(iface); + return NULL; +} + +/** + * netlbl_unlhsh_add - Adds a new entry to the unlabeled connection hash table + * @net: network namespace + * @dev_name: interface name + * @addr: IP address in network byte order + * @mask: address mask in network byte order + * @addr_len: length of address/mask (4 for IPv4, 16 for IPv6) + * @secid: LSM secid value for the entry + * @audit_info: NetLabel audit information + * + * Description: + * Adds a new entry to the unlabeled connection hash table. Returns zero on + * success, negative values on failure. + * + */ +static int netlbl_unlhsh_add(struct net *net, + const char *dev_name, + const void *addr, + const void *mask, + u32 addr_len, + u32 secid, + struct netlbl_audit *audit_info) +{ + int ret_val; + int ifindex; + struct net_device *dev; + struct netlbl_unlhsh_iface *iface; + struct in_addr *addr4, *mask4; + struct in6_addr *addr6, *mask6; + struct audit_buffer *audit_buf = NULL; + char *secctx = NULL; + u32 secctx_len; + + if (addr_len != sizeof(struct in_addr) && + addr_len != sizeof(struct in6_addr)) + return -EINVAL; + + rcu_read_lock(); + if (dev_name != NULL) { + dev = dev_get_by_name(net, dev_name); + if (dev == NULL) { + ret_val = -ENODEV; + goto unlhsh_add_return; + } + ifindex = dev->ifindex; + dev_put(dev); + iface = netlbl_unlhsh_search_iface(ifindex); + } else { + ifindex = 0; + iface = rcu_dereference(netlbl_unlhsh_def); + } + if (iface == NULL) { + iface = netlbl_unlhsh_add_iface(ifindex); + if (iface == NULL) { + ret_val = -ENOMEM; + goto unlhsh_add_return; + } + } + audit_buf = netlbl_audit_start_common(AUDIT_MAC_UNLBL_STCADD, + audit_info); + switch (addr_len) { + case sizeof(struct in_addr): + addr4 = (struct in_addr *)addr; + mask4 = (struct in_addr *)mask; + ret_val = netlbl_unlhsh_add_addr4(iface, addr4, mask4, secid); + if (audit_buf != NULL) + netlbl_unlabel_audit_addr4(audit_buf, + dev_name, + addr4->s_addr, + mask4->s_addr); + break; +#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE) + case sizeof(struct in6_addr): + addr6 = (struct in6_addr *)addr; + mask6 = (struct in6_addr *)mask; + ret_val = netlbl_unlhsh_add_addr6(iface, addr6, mask6, secid); + if (audit_buf != NULL) + netlbl_unlabel_audit_addr6(audit_buf, + dev_name, + addr6, mask6); + break; +#endif /* IPv6 */ + default: + ret_val = -EINVAL; + } + if (ret_val == 0) + atomic_inc(&netlabel_mgmt_protocount); + +unlhsh_add_return: + rcu_read_unlock(); + if (audit_buf != NULL) { + if (security_secid_to_secctx(secid, + &secctx, + &secctx_len) == 0) { + audit_log_format(audit_buf, " sec_obj=%s", secctx); + security_release_secctx(secctx, secctx_len); + } + audit_log_format(audit_buf, " res=%u", ret_val == 0 ? 1 : 0); + audit_log_end(audit_buf); + } + return ret_val; +} + +/** + * netlbl_unlhsh_remove_addr4 - Remove an IPv4 address entry + * @net: network namespace + * @iface: interface entry + * @addr: IP address + * @mask: IP address mask + * @audit_info: NetLabel audit information + * + * Description: + * Remove an IP address entry from the unlabeled connection hash table. + * Returns zero on success, negative values on failure. The caller is + * responsible for calling the rcu_read_[un]lock() functions. + * + */ +static int netlbl_unlhsh_remove_addr4(struct net *net, + struct netlbl_unlhsh_iface *iface, + const struct in_addr *addr, + const struct in_addr *mask, + struct netlbl_audit *audit_info) +{ + int ret_val = -ENOENT; + struct netlbl_unlhsh_addr4 *entry; + struct audit_buffer *audit_buf = NULL; + struct net_device *dev; + char *secctx = NULL; + u32 secctx_len; + + spin_lock(&netlbl_unlhsh_lock); + entry = netlbl_unlhsh_search_addr4(addr->s_addr, iface); + if (entry != NULL && + entry->addr == addr->s_addr && entry->mask == mask->s_addr) { + entry->valid = 0; + list_del_rcu(&entry->list); + ret_val = 0; + } + spin_unlock(&netlbl_unlhsh_lock); + + audit_buf = netlbl_audit_start_common(AUDIT_MAC_UNLBL_STCDEL, + audit_info); + if (audit_buf != NULL) { + dev = dev_get_by_index(net, iface->ifindex); + netlbl_unlabel_audit_addr4(audit_buf, + (dev != NULL ? dev->name : NULL), + entry->addr, entry->mask); + if (dev != NULL) + dev_put(dev); + if (security_secid_to_secctx(entry->secid, + &secctx, + &secctx_len) == 0) { + audit_log_format(audit_buf, " sec_obj=%s", secctx); + security_release_secctx(secctx, secctx_len); + } + audit_log_format(audit_buf, " res=%u", ret_val == 0 ? 1 : 0); + audit_log_end(audit_buf); + } + + if (ret_val == 0) + call_rcu(&entry->rcu, netlbl_unlhsh_free_addr4); + return ret_val; +} + +#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE) +/** + * netlbl_unlhsh_remove_addr6 - Remove an IPv6 address entry + * @net: network namespace + * @iface: interface entry + * @addr: IP address + * @mask: IP address mask + * @audit_info: NetLabel audit information + * + * Description: + * Remove an IP address entry from the unlabeled connection hash table. + * Returns zero on success, negative values on failure. The caller is + * responsible for calling the rcu_read_[un]lock() functions. + * + */ +static int netlbl_unlhsh_remove_addr6(struct net *net, + struct netlbl_unlhsh_iface *iface, + const struct in6_addr *addr, + const struct in6_addr *mask, + struct netlbl_audit *audit_info) +{ + int ret_val = -ENOENT; + struct netlbl_unlhsh_addr6 *entry; + struct audit_buffer *audit_buf = NULL; + struct net_device *dev; + char *secctx = NULL; + u32 secctx_len; + + spin_lock(&netlbl_unlhsh_lock); + entry = netlbl_unlhsh_search_addr6(addr, iface); + if (entry != NULL && + (ipv6_addr_equal(&entry->addr, addr) && + ipv6_addr_equal(&entry->mask, mask))) { + entry->valid = 0; + list_del_rcu(&entry->list); + ret_val = 0; + } + spin_unlock(&netlbl_unlhsh_lock); + + audit_buf = netlbl_audit_start_common(AUDIT_MAC_UNLBL_STCDEL, + audit_info); + if (audit_buf != NULL) { + dev = dev_get_by_index(net, iface->ifindex); + netlbl_unlabel_audit_addr6(audit_buf, + (dev != NULL ? dev->name : NULL), + addr, mask); + if (dev != NULL) + dev_put(dev); + if (security_secid_to_secctx(entry->secid, + &secctx, + &secctx_len) == 0) { + audit_log_format(audit_buf, " sec_obj=%s", secctx); + security_release_secctx(secctx, secctx_len); + } + audit_log_format(audit_buf, " res=%u", ret_val == 0 ? 1 : 0); + audit_log_end(audit_buf); + } + + if (ret_val == 0) + call_rcu(&entry->rcu, netlbl_unlhsh_free_addr6); + return ret_val; +} +#endif /* IPv6 */ + +/** + * netlbl_unlhsh_condremove_iface - Remove an interface entry + * @iface: the interface entry + * + * Description: + * Remove an interface entry from the unlabeled connection hash table if it is + * empty. An interface entry is considered to be empty if there are no + * address entries assigned to it. + * + */ +static void netlbl_unlhsh_condremove_iface(struct netlbl_unlhsh_iface *iface) +{ + struct netlbl_unlhsh_addr4 *iter4; + struct netlbl_unlhsh_addr6 *iter6; + + spin_lock(&netlbl_unlhsh_lock); + list_for_each_entry_rcu(iter4, &iface->addr4_list, list) + if (iter4->valid) + goto unlhsh_condremove_failure; + list_for_each_entry_rcu(iter6, &iface->addr6_list, list) + if (iter6->valid) + goto unlhsh_condremove_failure; + iface->valid = 0; + if (iface->ifindex > 0) + list_del_rcu(&iface->list); + else + rcu_assign_pointer(netlbl_unlhsh_def, NULL); + spin_unlock(&netlbl_unlhsh_lock); + + call_rcu(&iface->rcu, netlbl_unlhsh_free_iface); + return; + +unlhsh_condremove_failure: + spin_unlock(&netlbl_unlhsh_lock); + return; +} + +/** + * netlbl_unlhsh_remove - Remove an entry from the unlabeled hash table + * @net: network namespace + * @dev_name: interface name + * @addr: IP address in network byte order + * @mask: address mask in network byte order + * @addr_len: length of address/mask (4 for IPv4, 16 for IPv6) + * @audit_info: NetLabel audit information + * + * Description: + * Removes and existing entry from the unlabeled connection hash table. + * Returns zero on success, negative values on failure. + * + */ +static int netlbl_unlhsh_remove(struct net *net, + const char *dev_name, + const void *addr, + const void *mask, + u32 addr_len, + struct netlbl_audit *audit_info) +{ + int ret_val; + struct net_device *dev; + struct netlbl_unlhsh_iface *iface; + + if (addr_len != sizeof(struct in_addr) && + addr_len != sizeof(struct in6_addr)) + return -EINVAL; + + rcu_read_lock(); + if (dev_name != NULL) { + dev = dev_get_by_name(net, dev_name); + if (dev == NULL) { + ret_val = -ENODEV; + goto unlhsh_remove_return; + } + iface = netlbl_unlhsh_search_iface(dev->ifindex); + dev_put(dev); + } else + iface = rcu_dereference(netlbl_unlhsh_def); + if (iface == NULL) { + ret_val = -ENOENT; + goto unlhsh_remove_return; + } + switch (addr_len) { + case sizeof(struct in_addr): + ret_val = netlbl_unlhsh_remove_addr4(net, + iface, addr, mask, + audit_info); + break; +#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE) + case sizeof(struct in6_addr): + ret_val = netlbl_unlhsh_remove_addr6(net, + iface, addr, mask, + audit_info); + break; +#endif /* IPv6 */ + default: + ret_val = -EINVAL; + } + if (ret_val == 0) { + netlbl_unlhsh_condremove_iface(iface); + atomic_dec(&netlabel_mgmt_protocount); + } + +unlhsh_remove_return: + rcu_read_unlock(); + return ret_val; +} + +/* + * General Helper Functions + */ + +/** + * netlbl_unlhsh_netdev_handler - Network device notification handler + * @this: notifier block + * @event: the event + * @ptr: the network device (cast to void) + * + * Description: + * Handle network device events, although at present all we care about is a + * network device going away. In the case of a device going away we clear any + * related entries from the unlabeled connection hash table. + * + */ +static int netlbl_unlhsh_netdev_handler(struct notifier_block *this, + unsigned long event, + void *ptr) +{ + struct net_device *dev = ptr; + struct netlbl_unlhsh_iface *iface = NULL; + + if (dev->nd_net != &init_net) + return NOTIFY_DONE; + + /* XXX - should this be a check for NETDEV_DOWN or _UNREGISTER? */ + if (event == NETDEV_DOWN) { + spin_lock(&netlbl_unlhsh_lock); + iface = netlbl_unlhsh_search_iface(dev->ifindex); + if (iface != NULL && iface->valid) { + iface->valid = 0; + list_del_rcu(&iface->list); + } else + iface = NULL; + spin_unlock(&netlbl_unlhsh_lock); + } + + if (iface != NULL) + call_rcu(&iface->rcu, netlbl_unlhsh_free_iface); + + return NOTIFY_DONE; +} + /** * netlbl_unlabel_acceptflg_set - Set the unlabeled accept flag * @value: desired value @@ -84,11 +984,8 @@ static void netlbl_unlabel_acceptflg_set(u8 value, struct audit_buffer *audit_buf; u8 old_val; - spin_lock(&netlabel_unlabel_acceptflg_lock); old_val = netlabel_unlabel_acceptflg; netlabel_unlabel_acceptflg = value; - spin_unlock(&netlabel_unlabel_acceptflg_lock); - audit_buf = netlbl_audit_start_common(AUDIT_MAC_UNLBL_ALLOW, audit_info); if (audit_buf != NULL) { @@ -98,6 +995,48 @@ static void netlbl_unlabel_acceptflg_set(u8 value, } } +/** + * netlbl_unlabel_addrinfo_get - Get the IPv4/6 address information + * @info: the Generic NETLINK info block + * @addr: the IP address + * @mask: the IP address mask + * @len: the address length + * + * Description: + * Examine the Generic NETLINK message and extract the IP address information. + * Returns zero on success, negative values on failure. + * + */ +static int netlbl_unlabel_addrinfo_get(struct genl_info *info, + void **addr, + void **mask, + u32 *len) +{ + u32 addr_len; + + if (info->attrs[NLBL_UNLABEL_A_IPV4ADDR]) { + addr_len = nla_len(info->attrs[NLBL_UNLABEL_A_IPV4ADDR]); + if (addr_len != sizeof(struct in_addr) && + addr_len != nla_len(info->attrs[NLBL_UNLABEL_A_IPV4MASK])) + return -EINVAL; + *len = addr_len; + *addr = nla_data(info->attrs[NLBL_UNLABEL_A_IPV4ADDR]); + *mask = nla_data(info->attrs[NLBL_UNLABEL_A_IPV4MASK]); + return 0; + } else if (info->attrs[NLBL_UNLABEL_A_IPV6ADDR]) { + addr_len = nla_len(info->attrs[NLBL_UNLABEL_A_IPV6ADDR]); + if (addr_len != sizeof(struct in6_addr) && + addr_len != nla_len(info->attrs[NLBL_UNLABEL_A_IPV6MASK])) + return -EINVAL; + *len = addr_len; + *addr = nla_data(info->attrs[NLBL_UNLABEL_A_IPV6ADDR]); + *mask = nla_data(info->attrs[NLBL_UNLABEL_A_IPV6MASK]); + return 0; + } + + return -EINVAL; +} + /* * NetLabel Command Handlers */ @@ -155,11 +1094,9 @@ static int netlbl_unlabel_list(struct sk_buff *skb, struct genl_info *info) goto list_failure; } - rcu_read_lock(); ret_val = nla_put_u8(ans_skb, NLBL_UNLABEL_A_ACPTFLG, netlabel_unlabel_acceptflg); - rcu_read_unlock(); if (ret_val != 0) goto list_failure; @@ -175,11 +1112,489 @@ static int netlbl_unlabel_list(struct sk_buff *skb, struct genl_info *info) return ret_val; } +/** + * netlbl_unlabel_staticadd - Handle a STATICADD message + * @skb: the NETLINK buffer + * @info: the Generic NETLINK info block + * + * Description: + * Process a user generated STATICADD message and add a new unlabeled + * connection entry to the hash table. Returns zero on success, negative + * values on failure. + * + */ +static int netlbl_unlabel_staticadd(struct sk_buff *skb, + struct genl_info *info) +{ + int ret_val; + char *dev_name; + void *addr; + void *mask; + u32 addr_len; + u32 secid; + struct netlbl_audit audit_info; + + /* Don't allow users to add both IPv4 and IPv6 addresses for a + * single entry. However, allow users to create two entries, one each + * for IPv4 and IPv4, with the same LSM security context which should + * achieve the same result. */ + if (!info->attrs[NLBL_UNLABEL_A_SECCTX] || + !info->attrs[NLBL_UNLABEL_A_IFACE] || + !((!info->attrs[NLBL_UNLABEL_A_IPV4ADDR] || + !info->attrs[NLBL_UNLABEL_A_IPV4MASK]) ^ + (!info->attrs[NLBL_UNLABEL_A_IPV6ADDR] || + !info->attrs[NLBL_UNLABEL_A_IPV6MASK]))) + return -EINVAL; + + netlbl_netlink_auditinfo(skb, &audit_info); + + ret_val = netlbl_unlabel_addrinfo_get(info, &addr, &mask, &addr_len); + if (ret_val != 0) + return ret_val; + dev_name = nla_data(info->attrs[NLBL_UNLABEL_A_IFACE]); + ret_val = security_secctx_to_secid( + nla_data(info->attrs[NLBL_UNLABEL_A_SECCTX]), + nla_len(info->attrs[NLBL_UNLABEL_A_SECCTX]), + &secid); + if (ret_val != 0) + return ret_val; + + return netlbl_unlhsh_add(&init_net, + dev_name, addr, mask, addr_len, secid, + &audit_info); +} + +/** + * netlbl_unlabel_staticadddef - Handle a STATICADDDEF message + * @skb: the NETLINK buffer + * @info: the Generic NETLINK info block + * + * Description: + * Process a user generated STATICADDDEF message and add a new default + * unlabeled connection entry. Returns zero on success, negative values on + * failure. + * + */ +static int netlbl_unlabel_staticadddef(struct sk_buff *skb, + struct genl_info *info) +{ + int ret_val; + void *addr; + void *mask; + u32 addr_len; + u32 secid; + struct netlbl_audit audit_info; + + /* Don't allow users to add both IPv4 and IPv6 addresses for a + * single entry. However, allow users to create two entries, one each + * for IPv4 and IPv6, with the same LSM security context which should + * achieve the same result. */ + if (!info->attrs[NLBL_UNLABEL_A_SECCTX] || + !((!info->attrs[NLBL_UNLABEL_A_IPV4ADDR] || + !info->attrs[NLBL_UNLABEL_A_IPV4MASK]) ^ + (!info->attrs[NLBL_UNLABEL_A_IPV6ADDR] || + !info->attrs[NLBL_UNLABEL_A_IPV6MASK]))) + return -EINVAL; + + netlbl_netlink_auditinfo(skb, &audit_info); + + ret_val = netlbl_unlabel_addrinfo_get(info, &addr, &mask, &addr_len); + if (ret_val != 0) + return ret_val; + ret_val = security_secctx_to_secid( + nla_data(info->attrs[NLBL_UNLABEL_A_SECCTX]), + nla_len(info->attrs[NLBL_UNLABEL_A_SECCTX]), + &secid); + if (ret_val != 0) + return ret_val; + + return netlbl_unlhsh_add(&init_net, + NULL, addr, mask, addr_len, secid, + &audit_info); +} + +/** + * netlbl_unlabel_staticremove - Handle a STATICREMOVE message + * @skb: the NETLINK buffer + * @info: the Generic NETLINK info block + * + * Description: + * Process a user generated STATICREMOVE message and remove the specified + * unlabeled connection entry. Returns zero on success, negative values on + * failure. + * + */ +static int netlbl_unlabel_staticremove(struct sk_buff *skb, + struct genl_info *info) +{ + int ret_val; + char *dev_name; + void *addr; + void *mask; + u32 addr_len; + struct netlbl_audit audit_info; + + /* See the note in netlbl_unlabel_staticadd() about not allowing both + * IPv4 and IPv6 in the same entry. */ + if (!info->attrs[NLBL_UNLABEL_A_IFACE] || + !((!info->attrs[NLBL_UNLABEL_A_IPV4ADDR] || + !info->attrs[NLBL_UNLABEL_A_IPV4MASK]) ^ + (!info->attrs[NLBL_UNLABEL_A_IPV6ADDR] || + !info->attrs[NLBL_UNLABEL_A_IPV6MASK]))) + return -EINVAL; + + netlbl_netlink_auditinfo(skb, &audit_info); + + ret_val = netlbl_unlabel_addrinfo_get(info, &addr, &mask, &addr_len); + if (ret_val != 0) + return ret_val; + dev_name = nla_data(info->attrs[NLBL_UNLABEL_A_IFACE]); + + return netlbl_unlhsh_remove(&init_net, + dev_name, addr, mask, addr_len, + &audit_info); +} + +/** + * netlbl_unlabel_staticremovedef - Handle a STATICREMOVEDEF message + * @skb: the NETLINK buffer + * @info: the Generic NETLINK info block + * + * Description: + * Process a user generated STATICREMOVEDEF message and remove the default + * unlabeled connection entry. Returns zero on success, negative values on + * failure. + * + */ +static int netlbl_unlabel_staticremovedef(struct sk_buff *skb, + struct genl_info *info) +{ + int ret_val; + void *addr; + void *mask; + u32 addr_len; + struct netlbl_audit audit_info; + + /* See the note in netlbl_unlabel_staticadd() about not allowing both + * IPv4 and IPv6 in the same entry. */ + if (!((!info->attrs[NLBL_UNLABEL_A_IPV4ADDR] || + !info->attrs[NLBL_UNLABEL_A_IPV4MASK]) ^ + (!info->attrs[NLBL_UNLABEL_A_IPV6ADDR] || + !info->attrs[NLBL_UNLABEL_A_IPV6MASK]))) + return -EINVAL; + + netlbl_netlink_auditinfo(skb, &audit_info); + + ret_val = netlbl_unlabel_addrinfo_get(info, &addr, &mask, &addr_len); + if (ret_val != 0) + return ret_val; + + return netlbl_unlhsh_remove(&init_net, + NULL, addr, mask, addr_len, + &audit_info); +} + + +/** + * netlbl_unlabel_staticlist_gen - Generate messages for STATICLIST[DEF] + * @cmd: command/message + * @iface: the interface entry + * @addr4: the IPv4 address entry + * @addr6: the IPv6 address entry + * @arg: the netlbl_unlhsh_walk_arg structure + * + * Description: + * This function is designed to be used to generate a response for a + * STATICLIST or STATICLISTDEF message. When called either @addr4 or @addr6 + * can be specified, not both, the other unspecified entry should be set to + * NULL by the caller. Returns the size of the message on success, negative + * values on failure. + * + */ +static int netlbl_unlabel_staticlist_gen(u32 cmd, + const struct netlbl_unlhsh_iface *iface, + const struct netlbl_unlhsh_addr4 *addr4, + const struct netlbl_unlhsh_addr6 *addr6, + void *arg) +{ + int ret_val = -ENOMEM; + struct netlbl_unlhsh_walk_arg *cb_arg = arg; + struct net_device *dev; + void *data; + u32 secid; + char *secctx; + u32 secctx_len; + + data = genlmsg_put(cb_arg->skb, NETLINK_CB(cb_arg->nl_cb->skb).pid, + cb_arg->seq, &netlbl_unlabel_gnl_family, + NLM_F_MULTI, cmd); + if (data == NULL) + goto list_cb_failure; + + if (iface->ifindex > 0) { + dev = dev_get_by_index(&init_net, iface->ifindex); + ret_val = nla_put_string(cb_arg->skb, + NLBL_UNLABEL_A_IFACE, dev->name); + dev_put(dev); + if (ret_val != 0) + goto list_cb_failure; + } + + if (addr4) { + struct in_addr addr_struct; + + addr_struct.s_addr = addr4->addr; + ret_val = nla_put(cb_arg->skb, + NLBL_UNLABEL_A_IPV4ADDR, + sizeof(struct in_addr), + &addr_struct); + if (ret_val != 0) + goto list_cb_failure; + + addr_struct.s_addr = addr4->mask; + ret_val = nla_put(cb_arg->skb, + NLBL_UNLABEL_A_IPV4MASK, + sizeof(struct in_addr), + &addr_struct); + if (ret_val != 0) + goto list_cb_failure; + + secid = addr4->secid; + } else { + ret_val = nla_put(cb_arg->skb, + NLBL_UNLABEL_A_IPV6ADDR, + sizeof(struct in6_addr), + &addr6->addr); + if (ret_val != 0) + goto list_cb_failure; + + ret_val = nla_put(cb_arg->skb, + NLBL_UNLABEL_A_IPV6MASK, + sizeof(struct in6_addr), + &addr6->mask); + if (ret_val != 0) + goto list_cb_failure; + + secid = addr6->secid; + } + + ret_val = security_secid_to_secctx(secid, &secctx, &secctx_len); + if (ret_val != 0) + goto list_cb_failure; + ret_val = nla_put(cb_arg->skb, + NLBL_UNLABEL_A_SECCTX, + secctx_len, + secctx); + security_release_secctx(secctx, secctx_len); + if (ret_val != 0) + goto list_cb_failure; + + cb_arg->seq++; + return genlmsg_end(cb_arg->skb, data); + +list_cb_failure: + genlmsg_cancel(cb_arg->skb, data); + return ret_val; +} + +/** + * netlbl_unlabel_staticlist - Handle a STATICLIST message + * @skb: the NETLINK buffer + * @cb: the NETLINK callback + * + * Description: + * Process a user generated STATICLIST message and dump the unlabeled + * connection hash table in a form suitable for use in a kernel generated + * STATICLIST message. Returns the length of @skb. + * + */ +static int netlbl_unlabel_staticlist(struct sk_buff *skb, + struct netlink_callback *cb) +{ + struct netlbl_unlhsh_walk_arg cb_arg; + u32 skip_bkt = cb->args[0]; + u32 skip_chain = cb->args[1]; + u32 skip_addr4 = cb->args[2]; + u32 skip_addr6 = cb->args[3]; + u32 iter_bkt; + u32 iter_chain = 0, iter_addr4 = 0, iter_addr6 = 0; + struct netlbl_unlhsh_iface *iface; + struct netlbl_unlhsh_addr4 *addr4; + struct netlbl_unlhsh_addr6 *addr6; + + cb_arg.nl_cb = cb; + cb_arg.skb = skb; + cb_arg.seq = cb->nlh->nlmsg_seq; + + rcu_read_lock(); + for (iter_bkt = skip_bkt; + iter_bkt < rcu_dereference(netlbl_unlhsh)->size; + iter_bkt++, iter_chain = 0, iter_addr4 = 0, iter_addr6 = 0) { + list_for_each_entry_rcu(iface, + &rcu_dereference(netlbl_unlhsh)->tbl[iter_bkt], + list) { + if (!iface->valid || + iter_chain++ < skip_chain) + continue; + list_for_each_entry_rcu(addr4, + &iface->addr4_list, + list) { + if (!addr4->valid || iter_addr4++ < skip_addr4) + continue; + if (netlbl_unlabel_staticlist_gen( + NLBL_UNLABEL_C_STATICLIST, + iface, + addr4, + NULL, + &cb_arg) < 0) { + iter_addr4--; + iter_chain--; + goto unlabel_staticlist_return; + } + } + list_for_each_entry_rcu(addr6, + &iface->addr6_list, + list) { + if (!addr6->valid || iter_addr6++ < skip_addr6) + continue; + if (netlbl_unlabel_staticlist_gen( + NLBL_UNLABEL_C_STATICLIST, + iface, + NULL, + addr6, + &cb_arg) < 0) { + iter_addr6--; + iter_chain--; + goto unlabel_staticlist_return; + } + } + } + } + +unlabel_staticlist_return: + rcu_read_unlock(); + cb->args[0] = skip_bkt; + cb->args[1] = skip_chain; + cb->args[2] = skip_addr4; + cb->args[3] = skip_addr6; + return skb->len; +} + +/** + * netlbl_unlabel_staticlistdef - Handle a STATICLISTDEF message + * @skb: the NETLINK buffer + * @cb: the NETLINK callback + * + * Description: + * Process a user generated STATICLISTDEF message and dump the default + * unlabeled connection entry in a form suitable for use in a kernel generated + * STATICLISTDEF message. Returns the length of @skb. + * + */ +static int netlbl_unlabel_staticlistdef(struct sk_buff *skb, + struct netlink_callback *cb) +{ + struct netlbl_unlhsh_walk_arg cb_arg; + struct netlbl_unlhsh_iface *iface; + u32 skip_addr4 = cb->args[0]; + u32 skip_addr6 = cb->args[1]; + u32 iter_addr4 = 0, iter_addr6 = 0; + struct netlbl_unlhsh_addr4 *addr4; + struct netlbl_unlhsh_addr6 *addr6; + + cb_arg.nl_cb = cb; + cb_arg.skb = skb; + cb_arg.seq = cb->nlh->nlmsg_seq; + + rcu_read_lock(); + iface = rcu_dereference(netlbl_unlhsh_def); + if (iface == NULL || !iface->valid) + goto unlabel_staticlistdef_return; + + list_for_each_entry_rcu(addr4, &iface->addr4_list, list) { + if (!addr4->valid || iter_addr4++ < skip_addr4) + continue; + if (netlbl_unlabel_staticlist_gen(NLBL_UNLABEL_C_STATICLISTDEF, + iface, + addr4, + NULL, + &cb_arg) < 0) { + iter_addr4--; + goto unlabel_staticlistdef_return; + } + } + list_for_each_entry_rcu(addr6, &iface->addr6_list, list) { + if (addr6->valid || iter_addr6++ < skip_addr6) + continue; + if (netlbl_unlabel_staticlist_gen(NLBL_UNLABEL_C_STATICLISTDEF, + iface, + NULL, + addr6, + &cb_arg) < 0) { + iter_addr6--; + goto unlabel_staticlistdef_return; + } + } + +unlabel_staticlistdef_return: + rcu_read_unlock(); + cb->args[0] = skip_addr4; + cb->args[1] = skip_addr6; + return skb->len; +} /* * NetLabel Generic NETLINK Command Definitions */ +static struct genl_ops netlbl_unlabel_genl_c_staticadd = { + .cmd = NLBL_UNLABEL_C_STATICADD, + .flags = GENL_ADMIN_PERM, + .policy = netlbl_unlabel_genl_policy, + .doit = netlbl_unlabel_staticadd, + .dumpit = NULL, +}; + +static struct genl_ops netlbl_unlabel_genl_c_staticremove = { + .cmd = NLBL_UNLABEL_C_STATICREMOVE, + .flags = GENL_ADMIN_PERM, + .policy = netlbl_unlabel_genl_policy, + .doit = netlbl_unlabel_staticremove, + .dumpit = NULL, +}; + +static struct genl_ops netlbl_unlabel_genl_c_staticlist = { + .cmd = NLBL_UNLABEL_C_STATICLIST, + .flags = 0, + .policy = netlbl_unlabel_genl_policy, + .doit = NULL, + .dumpit = netlbl_unlabel_staticlist, +}; + +static struct genl_ops netlbl_unlabel_genl_c_staticadddef = { + .cmd = NLBL_UNLABEL_C_STATICADDDEF, + .flags = GENL_ADMIN_PERM, + .policy = netlbl_unlabel_genl_policy, + .doit = netlbl_unlabel_staticadddef, + .dumpit = NULL, +}; + +static struct genl_ops netlbl_unlabel_genl_c_staticremovedef = { + .cmd = NLBL_UNLABEL_C_STATICREMOVEDEF, + .flags = GENL_ADMIN_PERM, + .policy = netlbl_unlabel_genl_policy, + .doit = netlbl_unlabel_staticremovedef, + .dumpit = NULL, +}; + +static struct genl_ops netlbl_unlabel_genl_c_staticlistdef = { + .cmd = NLBL_UNLABEL_C_STATICLISTDEF, + .flags = 0, + .policy = netlbl_unlabel_genl_policy, + .doit = NULL, + .dumpit = netlbl_unlabel_staticlistdef, +}; + static struct genl_ops netlbl_unlabel_genl_c_accept = { .cmd = NLBL_UNLABEL_C_ACCEPT, .flags = GENL_ADMIN_PERM, @@ -196,7 +1611,6 @@ static struct genl_ops netlbl_unlabel_genl_c_list = { .dumpit = NULL, }; - /* * NetLabel Generic NETLINK Protocol Functions */ @@ -217,6 +1631,36 @@ int netlbl_unlabel_genl_init(void) if (ret_val != 0) return ret_val; + ret_val = genl_register_ops(&netlbl_unlabel_gnl_family, + &netlbl_unlabel_genl_c_staticadd); + if (ret_val != 0) + return ret_val; + + ret_val = genl_register_ops(&netlbl_unlabel_gnl_family, + &netlbl_unlabel_genl_c_staticremove); + if (ret_val != 0) + return ret_val; + + ret_val = genl_register_ops(&netlbl_unlabel_gnl_family, + &netlbl_unlabel_genl_c_staticlist); + if (ret_val != 0) + return ret_val; + + ret_val = genl_register_ops(&netlbl_unlabel_gnl_family, + &netlbl_unlabel_genl_c_staticadddef); + if (ret_val != 0) + return ret_val; + + ret_val = genl_register_ops(&netlbl_unlabel_gnl_family, + &netlbl_unlabel_genl_c_staticremovedef); + if (ret_val != 0) + return ret_val; + + ret_val = genl_register_ops(&netlbl_unlabel_gnl_family, + &netlbl_unlabel_genl_c_staticlistdef); + if (ret_val != 0) + return ret_val; + ret_val = genl_register_ops(&netlbl_unlabel_gnl_family, &netlbl_unlabel_genl_c_accept); if (ret_val != 0) @@ -234,8 +1678,58 @@ int netlbl_unlabel_genl_init(void) * NetLabel KAPI Hooks */ +static struct notifier_block netlbl_unlhsh_netdev_notifier = { + .notifier_call = netlbl_unlhsh_netdev_handler, +}; + +/** + * netlbl_unlabel_init - Initialize the unlabeled connection hash table + * @size: the number of bits to use for the hash buckets + * + * Description: + * Initializes the unlabeled connection hash table and registers a network + * device notification handler. This function should only be called by the + * NetLabel subsystem itself during initialization. Returns zero on success, + * non-zero values on error. + * + */ +int netlbl_unlabel_init(u32 size) +{ + u32 iter; + struct netlbl_unlhsh_tbl *hsh_tbl; + + if (size == 0) + return -EINVAL; + + hsh_tbl = kmalloc(sizeof(*hsh_tbl), GFP_KERNEL); + if (hsh_tbl == NULL) + return -ENOMEM; + hsh_tbl->size = 1 << size; + hsh_tbl->tbl = kcalloc(hsh_tbl->size, + sizeof(struct list_head), + GFP_KERNEL); + if (hsh_tbl->tbl == NULL) { + kfree(hsh_tbl); + return -ENOMEM; + } + for (iter = 0; iter < hsh_tbl->size; iter++) + INIT_LIST_HEAD(&hsh_tbl->tbl[iter]); + + rcu_read_lock(); + spin_lock(&netlbl_unlhsh_lock); + rcu_assign_pointer(netlbl_unlhsh, hsh_tbl); + spin_unlock(&netlbl_unlhsh_lock); + rcu_read_unlock(); + + register_netdevice_notifier(&netlbl_unlhsh_netdev_notifier); + + return 0; +} + /** * netlbl_unlabel_getattr - Get the security attributes for an unlabled packet + * @skb: the packet + * @family: protocol family * @secattr: the security attributes * * Description: @@ -243,19 +1737,52 @@ int netlbl_unlabel_genl_init(void) * them in @secattr. Returns zero on success and negative values on failure. * */ -int netlbl_unlabel_getattr(struct netlbl_lsm_secattr *secattr) +int netlbl_unlabel_getattr(const struct sk_buff *skb, + u16 family, + struct netlbl_lsm_secattr *secattr) { - int ret_val; + struct iphdr *hdr4; + struct ipv6hdr *hdr6; + struct netlbl_unlhsh_addr4 *addr4; + struct netlbl_unlhsh_addr6 *addr6; + struct netlbl_unlhsh_iface *iface; rcu_read_lock(); - if (netlabel_unlabel_acceptflg == 1) { - netlbl_secattr_init(secattr); - ret_val = 0; - } else - ret_val = -ENOMSG; + iface = netlbl_unlhsh_search_iface_def(skb->iif); + if (iface == NULL) + goto unlabel_getattr_nolabel; + switch (family) { + case PF_INET: + hdr4 = ip_hdr(skb); + addr4 = netlbl_unlhsh_search_addr4(hdr4->saddr, iface); + if (addr4 == NULL) + goto unlabel_getattr_nolabel; + secattr->attr.secid = addr4->secid; + break; +#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE) + case PF_INET6: + hdr6 = ipv6_hdr(skb); + addr6 = netlbl_unlhsh_search_addr6(&hdr6->saddr, iface); + if (addr6 == NULL) + goto unlabel_getattr_nolabel; + secattr->attr.secid = addr6->secid; + break; +#endif /* IPv6 */ + default: + goto unlabel_getattr_nolabel; + } rcu_read_unlock(); - return ret_val; + secattr->flags |= NETLBL_SECATTR_SECID; + secattr->type = NETLBL_NLTYPE_UNLABELED; + return 0; + +unlabel_getattr_nolabel: + rcu_read_unlock(); + if (netlabel_unlabel_acceptflg == 0) + return -ENOMSG; + secattr->type = NETLBL_NLTYPE_UNLABELED; + return 0; } /** diff --git a/net/netlabel/netlabel_unlabeled.h b/net/netlabel/netlabel_unlabeled.h index c2917fbb42cf..06b1301ac072 100644 --- a/net/netlabel/netlabel_unlabeled.h +++ b/net/netlabel/netlabel_unlabeled.h @@ -36,6 +36,116 @@ /* * The following NetLabel payloads are supported by the Unlabeled subsystem. * + * o STATICADD + * This message is sent from an application to add a new static label for + * incoming unlabeled connections. + * + * Required attributes: + * + * NLBL_UNLABEL_A_IFACE + * NLBL_UNLABEL_A_SECCTX + * + * If IPv4 is specified the following attributes are required: + * + * NLBL_UNLABEL_A_IPV4ADDR + * NLBL_UNLABEL_A_IPV4MASK + * + * If IPv6 is specified the following attributes are required: + * + * NLBL_UNLABEL_A_IPV6ADDR + * NLBL_UNLABEL_A_IPV6MASK + * + * o STATICREMOVE + * This message is sent from an application to remove an existing static + * label for incoming unlabeled connections. + * + * Required attributes: + * + * NLBL_UNLABEL_A_IFACE + * + * If IPv4 is specified the following attributes are required: + * + * NLBL_UNLABEL_A_IPV4ADDR + * NLBL_UNLABEL_A_IPV4MASK + * + * If IPv6 is specified the following attributes are required: + * + * NLBL_UNLABEL_A_IPV6ADDR + * NLBL_UNLABEL_A_IPV6MASK + * + * o STATICLIST + * This message can be sent either from an application or by the kernel in + * response to an application generated STATICLIST message. When sent by an + * application there is no payload and the NLM_F_DUMP flag should be set. + * The kernel should response with a series of the following messages. + * + * Required attributes: + * + * NLBL_UNLABEL_A_IFACE + * NLBL_UNLABEL_A_SECCTX + * + * If IPv4 is specified the following attributes are required: + * + * NLBL_UNLABEL_A_IPV4ADDR + * NLBL_UNLABEL_A_IPV4MASK + * + * If IPv6 is specified the following attributes are required: + * + * NLBL_UNLABEL_A_IPV6ADDR + * NLBL_UNLABEL_A_IPV6MASK + * + * o STATICADDDEF + * This message is sent from an application to set the default static + * label for incoming unlabeled connections. + * + * Required attribute: + * + * NLBL_UNLABEL_A_SECCTX + * + * If IPv4 is specified the following attributes are required: + * + * NLBL_UNLABEL_A_IPV4ADDR + * NLBL_UNLABEL_A_IPV4MASK + * + * If IPv6 is specified the following attributes are required: + * + * NLBL_UNLABEL_A_IPV6ADDR + * NLBL_UNLABEL_A_IPV6MASK + * + * o STATICREMOVEDEF + * This message is sent from an application to remove the existing default + * static label for incoming unlabeled connections. + * + * If IPv4 is specified the following attributes are required: + * + * NLBL_UNLABEL_A_IPV4ADDR + * NLBL_UNLABEL_A_IPV4MASK + * + * If IPv6 is specified the following attributes are required: + * + * NLBL_UNLABEL_A_IPV6ADDR + * NLBL_UNLABEL_A_IPV6MASK + * + * o STATICLISTDEF + * This message can be sent either from an application or by the kernel in + * response to an application generated STATICLISTDEF message. When sent by + * an application there is no payload and the NLM_F_DUMP flag should be set. + * The kernel should response with the following message. + * + * Required attribute: + * + * NLBL_UNLABEL_A_SECCTX + * + * If IPv4 is specified the following attributes are required: + * + * NLBL_UNLABEL_A_IPV4ADDR + * NLBL_UNLABEL_A_IPV4MASK + * + * If IPv6 is specified the following attributes are required: + * + * NLBL_UNLABEL_A_IPV6ADDR + * NLBL_UNLABEL_A_IPV6MASK + * * o ACCEPT * This message is sent from an application to specify if the kernel should * allow unlabled packets to pass if they do not match any of the static @@ -62,6 +172,12 @@ enum { NLBL_UNLABEL_C_UNSPEC, NLBL_UNLABEL_C_ACCEPT, NLBL_UNLABEL_C_LIST, + NLBL_UNLABEL_C_STATICADD, + NLBL_UNLABEL_C_STATICREMOVE, + NLBL_UNLABEL_C_STATICLIST, + NLBL_UNLABEL_C_STATICADDDEF, + NLBL_UNLABEL_C_STATICREMOVEDEF, + NLBL_UNLABEL_C_STATICLISTDEF, __NLBL_UNLABEL_C_MAX, }; #define NLBL_UNLABEL_C_MAX (__NLBL_UNLABEL_C_MAX - 1) @@ -73,6 +189,24 @@ enum { /* (NLA_U8) * if true then unlabeled packets are allowed to pass, else unlabeled * packets are rejected */ + NLBL_UNLABEL_A_IPV6ADDR, + /* (NLA_BINARY, struct in6_addr) + * an IPv6 address */ + NLBL_UNLABEL_A_IPV6MASK, + /* (NLA_BINARY, struct in6_addr) + * an IPv6 address mask */ + NLBL_UNLABEL_A_IPV4ADDR, + /* (NLA_BINARY, struct in_addr) + * an IPv4 address */ + NLBL_UNLABEL_A_IPV4MASK, + /* (NLA_BINARY, struct in_addr) + * and IPv4 address mask */ + NLBL_UNLABEL_A_IFACE, + /* (NLA_NULL_STRING) + * network interface */ + NLBL_UNLABEL_A_SECCTX, + /* (NLA_BINARY) + * a LSM specific security context */ __NLBL_UNLABEL_A_MAX, }; #define NLBL_UNLABEL_A_MAX (__NLBL_UNLABEL_A_MAX - 1) @@ -80,8 +214,17 @@ enum { /* NetLabel protocol functions */ int netlbl_unlabel_genl_init(void); +/* Unlabeled connection hash table size */ +/* XXX - currently this number is an uneducated guess */ +#define NETLBL_UNLHSH_BITSIZE 7 + +/* General Unlabeled init function */ +int netlbl_unlabel_init(u32 size); + /* Process Unlabeled incoming network packets */ -int netlbl_unlabel_getattr(struct netlbl_lsm_secattr *secattr); +int netlbl_unlabel_getattr(const struct sk_buff *skb, + u16 family, + struct netlbl_lsm_secattr *secattr); /* Set the default configuration to allow Unlabeled packets */ int netlbl_unlabel_defconf(void); diff --git a/security/Kconfig b/security/Kconfig index 8086e61058e3..389e151e3b68 100644 --- a/security/Kconfig +++ b/security/Kconfig @@ -76,6 +76,7 @@ config SECURITY_NETWORK_XFRM config SECURITY_CAPABILITIES bool "Default Linux Capabilities" depends on SECURITY + default y help This enables the "default" Linux capabilities functionality. If you are unsure how to answer this question, answer Y. diff --git a/security/selinux/Kconfig b/security/selinux/Kconfig index b32a459c0683..2b517d618672 100644 --- a/security/selinux/Kconfig +++ b/security/selinux/Kconfig @@ -145,7 +145,7 @@ config SECURITY_SELINUX_POLICYDB_VERSION_MAX config SECURITY_SELINUX_POLICYDB_VERSION_MAX_VALUE int "NSA SELinux maximum supported policy format version value" depends on SECURITY_SELINUX_POLICYDB_VERSION_MAX - range 15 21 + range 15 22 default 19 help This option sets the value for the maximum policy format version diff --git a/security/selinux/Makefile b/security/selinux/Makefile index dc3502e30b19..00afd85f1edb 100644 --- a/security/selinux/Makefile +++ b/security/selinux/Makefile @@ -4,7 +4,14 @@ obj-$(CONFIG_SECURITY_SELINUX) := selinux.o ss/ -selinux-y := avc.o hooks.o selinuxfs.o netlink.o nlmsgtab.o netif.o exports.o +selinux-y := avc.o \ + hooks.o \ + selinuxfs.o \ + netlink.o \ + nlmsgtab.o \ + netif.o \ + netnode.o \ + exports.o selinux-$(CONFIG_SECURITY_NETWORK_XFRM) += xfrm.o diff --git a/security/selinux/avc.c b/security/selinux/avc.c index 81b3dff3cbf0..e8529e2f51e5 100644 --- a/security/selinux/avc.c +++ b/security/selinux/avc.c @@ -661,9 +661,18 @@ void avc_audit(u32 ssid, u32 tsid, "daddr", "dest"); break; } - if (a->u.net.netif) - audit_log_format(ab, " netif=%s", - a->u.net.netif); + if (a->u.net.netif > 0) { + struct net_device *dev; + + /* NOTE: we always use init's namespace */ + dev = dev_get_by_index(&init_net, + a->u.net.netif); + if (dev) { + audit_log_format(ab, " netif=%s", + dev->name); + dev_put(dev); + } + } break; } } diff --git a/security/selinux/exports.c b/security/selinux/exports.c index b6f96943be1f..87d2bb3ea355 100644 --- a/security/selinux/exports.c +++ b/security/selinux/exports.c @@ -17,10 +17,14 @@ #include #include #include +#include #include "security.h" #include "objsec.h" +/* SECMARK reference count */ +extern atomic_t selinux_secmark_refcount; + int selinux_sid_to_string(u32 sid, char **ctx, u32 *ctxlen) { if (selinux_enabled) @@ -74,7 +78,7 @@ int selinux_string_to_sid(char *str, u32 *sid) } EXPORT_SYMBOL_GPL(selinux_string_to_sid); -int selinux_relabel_packet_permission(u32 sid) +int selinux_secmark_relabel_packet_permission(u32 sid) { if (selinux_enabled) { struct task_security_struct *tsec = current->security; @@ -84,4 +88,16 @@ int selinux_relabel_packet_permission(u32 sid) } return 0; } -EXPORT_SYMBOL_GPL(selinux_relabel_packet_permission); +EXPORT_SYMBOL_GPL(selinux_secmark_relabel_packet_permission); + +void selinux_secmark_refcount_inc(void) +{ + atomic_inc(&selinux_secmark_refcount); +} +EXPORT_SYMBOL_GPL(selinux_secmark_refcount_inc); + +void selinux_secmark_refcount_dec(void) +{ + atomic_dec(&selinux_secmark_refcount); +} +EXPORT_SYMBOL_GPL(selinux_secmark_refcount_dec); diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 64d414efb404..be6de0b8734f 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -12,8 +12,8 @@ * Copyright (C) 2003 Red Hat, Inc., James Morris * Copyright (C) 2004-2005 Trusted Computer Solutions, Inc. * - * Copyright (C) 2006 Hewlett-Packard Development Company, L.P. - * Paul Moore, + * Copyright (C) 2006, 2007 Hewlett-Packard Development Company, L.P. + * Paul Moore * Copyright (C) 2007 Hitachi Software Engineering Co., Ltd. * Yuichi Nakamura * @@ -50,8 +50,11 @@ #include #include /* for local_port_range[] */ #include /* struct or_callable used in sock_rcv_skb */ +#include +#include #include #include +#include #include #include #include /* for network interface checks */ @@ -76,6 +79,7 @@ #include "avc.h" #include "objsec.h" #include "netif.h" +#include "netnode.h" #include "xfrm.h" #include "netlabel.h" @@ -89,6 +93,9 @@ extern int selinux_nlmsg_lookup(u16 sclass, u16 nlmsg_type, u32 *perm); extern int selinux_compat_net; extern struct security_operations *security_ops; +/* SECMARK reference count */ +atomic_t selinux_secmark_refcount = ATOMIC_INIT(0); + #ifdef CONFIG_SECURITY_SELINUX_DEVELOP int selinux_enforcing = 0; @@ -155,6 +162,21 @@ static int selinux_getsecurity(u32 sid, void *buffer, size_t size) return len; } +/** + * selinux_secmark_enabled - Check to see if SECMARK is currently enabled + * + * Description: + * This function checks the SECMARK reference counter to see if any SECMARK + * targets are currently configured, if the reference counter is greater than + * zero SECMARK is considered to be enabled. Returns true (1) if SECMARK is + * enabled, false (0) if SECMARK is disabled. + * + */ +static int selinux_secmark_enabled(void) +{ + return (atomic_read(&selinux_secmark_refcount) > 0); +} + /* Allocate and free functions for each kind of security blob. */ static int task_alloc_security(struct task_struct *task) @@ -561,8 +583,8 @@ static int bad_option(struct superblock_security_struct *sbsec, char flag, * Allow filesystems with binary mount data to explicitly set mount point * labeling information. */ -int selinux_set_mnt_opts(struct super_block *sb, char **mount_options, - int *flags, int num_opts) +static int selinux_set_mnt_opts(struct super_block *sb, char **mount_options, + int *flags, int num_opts) { int rc = 0, i; struct task_security_struct *tsec = current->security; @@ -3395,7 +3417,7 @@ static int selinux_parse_skb_ipv6(struct sk_buff *skb, #endif /* IPV6 */ static int selinux_parse_skb(struct sk_buff *skb, struct avc_audit_data *ad, - char **addrp, int *len, int src, u8 *proto) + char **addrp, int src, u8 *proto) { int ret = 0; @@ -3404,7 +3426,6 @@ static int selinux_parse_skb(struct sk_buff *skb, struct avc_audit_data *ad, ret = selinux_parse_skb_ipv4(skb, ad, proto); if (ret || !addrp) break; - *len = 4; *addrp = (char *)(src ? &ad->u.net.v4info.saddr : &ad->u.net.v4info.daddr); break; @@ -3414,7 +3435,6 @@ static int selinux_parse_skb(struct sk_buff *skb, struct avc_audit_data *ad, ret = selinux_parse_skb_ipv6(skb, ad, proto); if (ret || !addrp) break; - *len = 16; *addrp = (char *)(src ? &ad->u.net.v6info.saddr : &ad->u.net.v6info.daddr); break; @@ -3423,36 +3443,48 @@ static int selinux_parse_skb(struct sk_buff *skb, struct avc_audit_data *ad, break; } + if (unlikely(ret)) + printk(KERN_WARNING + "SELinux: failure in selinux_parse_skb()," + " unable to parse packet\n"); + return ret; } /** - * selinux_skb_extlbl_sid - Determine the external label of a packet + * selinux_skb_peerlbl_sid - Determine the peer label of a packet * @skb: the packet - * @sid: the packet's SID + * @family: protocol family + * @sid: the packet's peer label SID * * Description: - * Check the various different forms of external packet labeling and determine - * the external SID for the packet. If only one form of external labeling is - * present then it is used, if both labeled IPsec and NetLabel labels are - * present then the SELinux type information is taken from the labeled IPsec - * SA and the MLS sensitivity label information is taken from the NetLabel - * security attributes. This bit of "magic" is done in the call to - * selinux_netlbl_skbuff_getsid(). + * Check the various different forms of network peer labeling and determine + * the peer label/SID for the packet; most of the magic actually occurs in + * the security server function security_net_peersid_cmp(). The function + * returns zero if the value in @sid is valid (although it may be SECSID_NULL) + * or -EACCES if @sid is invalid due to inconsistencies with the different + * peer labels. * */ -static void selinux_skb_extlbl_sid(struct sk_buff *skb, u32 *sid) +static int selinux_skb_peerlbl_sid(struct sk_buff *skb, u16 family, u32 *sid) { + int err; u32 xfrm_sid; u32 nlbl_sid; + u32 nlbl_type; selinux_skb_xfrm_sid(skb, &xfrm_sid); - if (selinux_netlbl_skbuff_getsid(skb, - (xfrm_sid == SECSID_NULL ? - SECINITSID_NETMSG : xfrm_sid), - &nlbl_sid) != 0) - nlbl_sid = SECSID_NULL; - *sid = (nlbl_sid == SECSID_NULL ? xfrm_sid : nlbl_sid); + selinux_netlbl_skbuff_getsid(skb, family, &nlbl_type, &nlbl_sid); + + err = security_net_peersid_resolve(nlbl_sid, nlbl_type, xfrm_sid, sid); + if (unlikely(err)) { + printk(KERN_WARNING + "SELinux: failure in selinux_skb_peerlbl_sid()," + " unable to determine packet's peer label\n"); + return -EACCES; + } + + return 0; } /* socket security operations */ @@ -3518,6 +3550,7 @@ static int selinux_socket_post_create(struct socket *sock, int family, if (sock->sk) { sksec = sock->sk->sk_security; sksec->sid = isec->sid; + sksec->sclass = isec->sclass; err = selinux_netlbl_socket_post_create(sock); } @@ -3610,7 +3643,7 @@ static int selinux_socket_bind(struct socket *sock, struct sockaddr *address, in break; } - err = security_node_sid(family, addrp, addrlen, &sid); + err = sel_netnode_sid(addrp, family, &sid); if (err) goto out; @@ -3821,131 +3854,182 @@ static int selinux_socket_unix_may_send(struct socket *sock, return 0; } -static int selinux_sock_rcv_skb_compat(struct sock *sk, struct sk_buff *skb, - struct avc_audit_data *ad, u16 family, char *addrp, int len) +static int selinux_inet_sys_rcv_skb(int ifindex, char *addrp, u16 family, + u32 peer_sid, + struct avc_audit_data *ad) { - int err = 0; - u32 netif_perm, node_perm, node_sid, if_sid, recv_perm = 0; - struct socket *sock; - u16 sock_class = 0; - u32 sock_sid = 0; + int err; + u32 if_sid; + u32 node_sid; - read_lock_bh(&sk->sk_callback_lock); - sock = sk->sk_socket; - if (sock) { - struct inode *inode; - inode = SOCK_INODE(sock); - if (inode) { - struct inode_security_struct *isec; - isec = inode->i_security; - sock_sid = isec->sid; - sock_class = isec->sclass; - } - } - read_unlock_bh(&sk->sk_callback_lock); - if (!sock_sid) - goto out; - - if (!skb->dev) - goto out; - - err = sel_netif_sids(skb->dev, &if_sid, NULL); + err = sel_netif_sid(ifindex, &if_sid); if (err) - goto out; + return err; + err = avc_has_perm(peer_sid, if_sid, + SECCLASS_NETIF, NETIF__INGRESS, ad); + if (err) + return err; - switch (sock_class) { + err = sel_netnode_sid(addrp, family, &node_sid); + if (err) + return err; + return avc_has_perm(peer_sid, node_sid, + SECCLASS_NODE, NODE__RECVFROM, ad); +} + +static int selinux_sock_rcv_skb_iptables_compat(struct sock *sk, + struct sk_buff *skb, + struct avc_audit_data *ad, + u16 family, + char *addrp) +{ + int err; + struct sk_security_struct *sksec = sk->sk_security; + u16 sk_class; + u32 netif_perm, node_perm, recv_perm; + u32 port_sid, node_sid, if_sid, sk_sid; + + sk_sid = sksec->sid; + sk_class = sksec->sclass; + + switch (sk_class) { case SECCLASS_UDP_SOCKET: netif_perm = NETIF__UDP_RECV; node_perm = NODE__UDP_RECV; recv_perm = UDP_SOCKET__RECV_MSG; break; - case SECCLASS_TCP_SOCKET: netif_perm = NETIF__TCP_RECV; node_perm = NODE__TCP_RECV; recv_perm = TCP_SOCKET__RECV_MSG; break; - case SECCLASS_DCCP_SOCKET: netif_perm = NETIF__DCCP_RECV; node_perm = NODE__DCCP_RECV; recv_perm = DCCP_SOCKET__RECV_MSG; break; - default: netif_perm = NETIF__RAWIP_RECV; node_perm = NODE__RAWIP_RECV; + recv_perm = 0; break; } - err = avc_has_perm(sock_sid, if_sid, SECCLASS_NETIF, netif_perm, ad); + err = sel_netif_sid(skb->iif, &if_sid); if (err) - goto out; + return err; + err = avc_has_perm(sk_sid, if_sid, SECCLASS_NETIF, netif_perm, ad); + if (err) + return err; - err = security_node_sid(family, addrp, len, &node_sid); + err = sel_netnode_sid(addrp, family, &node_sid); if (err) - goto out; - - err = avc_has_perm(sock_sid, node_sid, SECCLASS_NODE, node_perm, ad); + return err; + err = avc_has_perm(sk_sid, node_sid, SECCLASS_NODE, node_perm, ad); if (err) - goto out; + return err; - if (recv_perm) { - u32 port_sid; + if (!recv_perm) + return 0; + err = security_port_sid(sk->sk_family, sk->sk_type, + sk->sk_protocol, ntohs(ad->u.net.sport), + &port_sid); + if (unlikely(err)) { + printk(KERN_WARNING + "SELinux: failure in" + " selinux_sock_rcv_skb_iptables_compat()," + " network port label not found\n"); + return err; + } + return avc_has_perm(sk_sid, port_sid, sk_class, recv_perm, ad); +} - err = security_port_sid(sk->sk_family, sk->sk_type, - sk->sk_protocol, ntohs(ad->u.net.sport), - &port_sid); +static int selinux_sock_rcv_skb_compat(struct sock *sk, struct sk_buff *skb, + struct avc_audit_data *ad, + u16 family, char *addrp) +{ + int err; + struct sk_security_struct *sksec = sk->sk_security; + u32 peer_sid; + u32 sk_sid = sksec->sid; + + if (selinux_compat_net) + err = selinux_sock_rcv_skb_iptables_compat(sk, skb, ad, + family, addrp); + else + err = avc_has_perm(sk_sid, skb->secmark, SECCLASS_PACKET, + PACKET__RECV, ad); + if (err) + return err; + + if (selinux_policycap_netpeer) { + err = selinux_skb_peerlbl_sid(skb, family, &peer_sid); if (err) - goto out; - - err = avc_has_perm(sock_sid, port_sid, - sock_class, recv_perm, ad); + return err; + err = avc_has_perm(sk_sid, peer_sid, + SECCLASS_PEER, PEER__RECV, ad); + } else { + err = selinux_netlbl_sock_rcv_skb(sksec, skb, family, ad); + if (err) + return err; + err = selinux_xfrm_sock_rcv_skb(sksec->sid, skb, ad); } -out: return err; } static int selinux_socket_sock_rcv_skb(struct sock *sk, struct sk_buff *skb) { - u16 family; - char *addrp; - int len, err = 0; - struct avc_audit_data ad; + int err; struct sk_security_struct *sksec = sk->sk_security; + u16 family = sk->sk_family; + u32 sk_sid = sksec->sid; + struct avc_audit_data ad; + char *addrp; - family = sk->sk_family; if (family != PF_INET && family != PF_INET6) - goto out; + return 0; /* Handle mapped IPv4 packets arriving via IPv6 sockets */ if (family == PF_INET6 && skb->protocol == htons(ETH_P_IP)) family = PF_INET; AVC_AUDIT_DATA_INIT(&ad, NET); - ad.u.net.netif = skb->dev ? skb->dev->name : "[unknown]"; + ad.u.net.netif = skb->iif; ad.u.net.family = family; - - err = selinux_parse_skb(skb, &ad, &addrp, &len, 1, NULL); + err = selinux_parse_skb(skb, &ad, &addrp, 1, NULL); if (err) - goto out; + return err; - if (selinux_compat_net) - err = selinux_sock_rcv_skb_compat(sk, skb, &ad, family, - addrp, len); - else - err = avc_has_perm(sksec->sid, skb->secmark, SECCLASS_PACKET, + /* If any sort of compatibility mode is enabled then handoff processing + * to the selinux_sock_rcv_skb_compat() function to deal with the + * special handling. We do this in an attempt to keep this function + * as fast and as clean as possible. */ + if (selinux_compat_net || !selinux_policycap_netpeer) + return selinux_sock_rcv_skb_compat(sk, skb, &ad, + family, addrp); + + if (netlbl_enabled() || selinux_xfrm_enabled()) { + u32 peer_sid; + + err = selinux_skb_peerlbl_sid(skb, family, &peer_sid); + if (err) + return err; + err = selinux_inet_sys_rcv_skb(skb->iif, addrp, family, + peer_sid, &ad); + if (err) + return err; + err = avc_has_perm(sk_sid, peer_sid, SECCLASS_PEER, + PEER__RECV, &ad); + } + + if (selinux_secmark_enabled()) { + err = avc_has_perm(sk_sid, skb->secmark, SECCLASS_PACKET, PACKET__RECV, &ad); - if (err) - goto out; + if (err) + return err; + } - err = selinux_netlbl_sock_rcv_skb(sksec, skb, &ad); - if (err) - goto out; - - err = selinux_xfrm_sock_rcv_skb(sksec->sid, skb, &ad); -out: return err; } @@ -3996,18 +4080,25 @@ static int selinux_socket_getpeersec_stream(struct socket *sock, char __user *op static int selinux_socket_getpeersec_dgram(struct socket *sock, struct sk_buff *skb, u32 *secid) { u32 peer_secid = SECSID_NULL; - int err = 0; + u16 family; - if (sock && sock->sk->sk_family == PF_UNIX) + if (sock) + family = sock->sk->sk_family; + else if (skb && skb->sk) + family = skb->sk->sk_family; + else + goto out; + + if (sock && family == PF_UNIX) selinux_get_inode_sid(SOCK_INODE(sock), &peer_secid); else if (skb) - selinux_skb_extlbl_sid(skb, &peer_secid); + selinux_skb_peerlbl_sid(skb, family, &peer_secid); - if (peer_secid == SECSID_NULL) - err = -EINVAL; +out: *secid = peer_secid; - - return err; + if (peer_secid == SECSID_NULL) + return -EINVAL; + return 0; } static int selinux_sk_alloc_security(struct sock *sk, int family, gfp_t priority) @@ -4027,6 +4118,7 @@ static void selinux_sk_clone_security(const struct sock *sk, struct sock *newsk) newssec->sid = ssec->sid; newssec->peer_sid = ssec->peer_sid; + newssec->sclass = ssec->sclass; selinux_netlbl_sk_security_clone(ssec, newssec); } @@ -4050,6 +4142,7 @@ static void selinux_sock_graft(struct sock* sk, struct socket *parent) if (sk->sk_family == PF_INET || sk->sk_family == PF_INET6 || sk->sk_family == PF_UNIX) isec->sid = sksec->sid; + sksec->sclass = isec->sclass; selinux_netlbl_sock_graft(sk, parent); } @@ -4062,7 +4155,9 @@ static int selinux_inet_conn_request(struct sock *sk, struct sk_buff *skb, u32 newsid; u32 peersid; - selinux_skb_extlbl_sid(skb, &peersid); + err = selinux_skb_peerlbl_sid(skb, sk->sk_family, &peersid); + if (err) + return err; if (peersid == SECSID_NULL) { req->secid = sksec->sid; req->peer_secid = SECSID_NULL; @@ -4100,7 +4195,7 @@ static void selinux_inet_conn_established(struct sock *sk, { struct sk_security_struct *sksec = sk->sk_security; - selinux_skb_extlbl_sid(skb, &sksec->peer_sid); + selinux_skb_peerlbl_sid(skb, sk->sk_family, &sksec->peer_sid); } static void selinux_req_classify_flow(const struct request_sock *req, @@ -4147,149 +4242,260 @@ static int selinux_nlmsg_perm(struct sock *sk, struct sk_buff *skb) #ifdef CONFIG_NETFILTER -static int selinux_ip_postroute_last_compat(struct sock *sk, struct net_device *dev, - struct avc_audit_data *ad, - u16 family, char *addrp, int len) +static unsigned int selinux_ip_forward(struct sk_buff *skb, int ifindex, + u16 family) { - int err = 0; - u32 netif_perm, node_perm, node_sid, if_sid, send_perm = 0; - struct socket *sock; - struct inode *inode; - struct inode_security_struct *isec; + char *addrp; + u32 peer_sid; + struct avc_audit_data ad; + u8 secmark_active; + u8 peerlbl_active; - sock = sk->sk_socket; - if (!sock) - goto out; + if (!selinux_policycap_netpeer) + return NF_ACCEPT; - inode = SOCK_INODE(sock); - if (!inode) - goto out; + secmark_active = selinux_secmark_enabled(); + peerlbl_active = netlbl_enabled() || selinux_xfrm_enabled(); + if (!secmark_active && !peerlbl_active) + return NF_ACCEPT; - isec = inode->i_security; - - err = sel_netif_sids(dev, &if_sid, NULL); - if (err) - goto out; + AVC_AUDIT_DATA_INIT(&ad, NET); + ad.u.net.netif = ifindex; + ad.u.net.family = family; + if (selinux_parse_skb(skb, &ad, &addrp, 1, NULL) != 0) + return NF_DROP; - switch (isec->sclass) { + if (selinux_skb_peerlbl_sid(skb, family, &peer_sid) != 0) + return NF_DROP; + + if (peerlbl_active) + if (selinux_inet_sys_rcv_skb(ifindex, addrp, family, + peer_sid, &ad) != 0) + return NF_DROP; + + if (secmark_active) + if (avc_has_perm(peer_sid, skb->secmark, + SECCLASS_PACKET, PACKET__FORWARD_IN, &ad)) + return NF_DROP; + + return NF_ACCEPT; +} + +static unsigned int selinux_ipv4_forward(unsigned int hooknum, + struct sk_buff *skb, + const struct net_device *in, + const struct net_device *out, + int (*okfn)(struct sk_buff *)) +{ + return selinux_ip_forward(skb, in->ifindex, PF_INET); +} + +#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE) +static unsigned int selinux_ipv6_forward(unsigned int hooknum, + struct sk_buff *skb, + const struct net_device *in, + const struct net_device *out, + int (*okfn)(struct sk_buff *)) +{ + return selinux_ip_forward(skb, in->ifindex, PF_INET6); +} +#endif /* IPV6 */ + +static int selinux_ip_postroute_iptables_compat(struct sock *sk, + int ifindex, + struct avc_audit_data *ad, + u16 family, char *addrp) +{ + int err; + struct sk_security_struct *sksec = sk->sk_security; + u16 sk_class; + u32 netif_perm, node_perm, send_perm; + u32 port_sid, node_sid, if_sid, sk_sid; + + sk_sid = sksec->sid; + sk_class = sksec->sclass; + + switch (sk_class) { case SECCLASS_UDP_SOCKET: netif_perm = NETIF__UDP_SEND; node_perm = NODE__UDP_SEND; send_perm = UDP_SOCKET__SEND_MSG; break; - case SECCLASS_TCP_SOCKET: netif_perm = NETIF__TCP_SEND; node_perm = NODE__TCP_SEND; send_perm = TCP_SOCKET__SEND_MSG; break; - case SECCLASS_DCCP_SOCKET: netif_perm = NETIF__DCCP_SEND; node_perm = NODE__DCCP_SEND; send_perm = DCCP_SOCKET__SEND_MSG; break; - default: netif_perm = NETIF__RAWIP_SEND; node_perm = NODE__RAWIP_SEND; + send_perm = 0; break; } - err = avc_has_perm(isec->sid, if_sid, SECCLASS_NETIF, netif_perm, ad); + err = sel_netif_sid(ifindex, &if_sid); if (err) - goto out; + return err; + err = avc_has_perm(sk_sid, if_sid, SECCLASS_NETIF, netif_perm, ad); + return err; - err = security_node_sid(family, addrp, len, &node_sid); + err = sel_netnode_sid(addrp, family, &node_sid); if (err) - goto out; - - err = avc_has_perm(isec->sid, node_sid, SECCLASS_NODE, node_perm, ad); + return err; + err = avc_has_perm(sk_sid, node_sid, SECCLASS_NODE, node_perm, ad); if (err) - goto out; + return err; - if (send_perm) { - u32 port_sid; - - err = security_port_sid(sk->sk_family, - sk->sk_type, - sk->sk_protocol, - ntohs(ad->u.net.dport), - &port_sid); - if (err) - goto out; + if (send_perm != 0) + return 0; - err = avc_has_perm(isec->sid, port_sid, isec->sclass, - send_perm, ad); + err = security_port_sid(sk->sk_family, sk->sk_type, + sk->sk_protocol, ntohs(ad->u.net.dport), + &port_sid); + if (unlikely(err)) { + printk(KERN_WARNING + "SELinux: failure in" + " selinux_ip_postroute_iptables_compat()," + " network port label not found\n"); + return err; } -out: - return err; + return avc_has_perm(sk_sid, port_sid, sk_class, send_perm, ad); } -static unsigned int selinux_ip_postroute_last(unsigned int hooknum, - struct sk_buff *skb, - const struct net_device *in, - const struct net_device *out, - int (*okfn)(struct sk_buff *), - u16 family) +static unsigned int selinux_ip_postroute_compat(struct sk_buff *skb, + int ifindex, + struct avc_audit_data *ad, + u16 family, + char *addrp, + u8 proto) { - char *addrp; - int len, err = 0; - struct sock *sk; - struct avc_audit_data ad; - struct net_device *dev = (struct net_device *)out; + struct sock *sk = skb->sk; struct sk_security_struct *sksec; - u8 proto; - - sk = skb->sk; - if (!sk) - goto out; + if (sk == NULL) + return NF_ACCEPT; sksec = sk->sk_security; - AVC_AUDIT_DATA_INIT(&ad, NET); - ad.u.net.netif = dev->name; - ad.u.net.family = family; + if (selinux_compat_net) { + if (selinux_ip_postroute_iptables_compat(skb->sk, ifindex, + ad, family, addrp)) + return NF_DROP; + } else { + if (avc_has_perm(sksec->sid, skb->secmark, + SECCLASS_PACKET, PACKET__SEND, ad)) + return NF_DROP; + } - err = selinux_parse_skb(skb, &ad, &addrp, &len, 0, &proto); - if (err) - goto out; + if (selinux_policycap_netpeer) + if (selinux_xfrm_postroute_last(sksec->sid, skb, ad, proto)) + return NF_DROP; - if (selinux_compat_net) - err = selinux_ip_postroute_last_compat(sk, dev, &ad, - family, addrp, len); - else - err = avc_has_perm(sksec->sid, skb->secmark, SECCLASS_PACKET, - PACKET__SEND, &ad); - - if (err) - goto out; - - err = selinux_xfrm_postroute_last(sksec->sid, skb, &ad, proto); -out: - return err ? NF_DROP : NF_ACCEPT; + return NF_ACCEPT; } -static unsigned int selinux_ipv4_postroute_last(unsigned int hooknum, - struct sk_buff *skb, - const struct net_device *in, - const struct net_device *out, - int (*okfn)(struct sk_buff *)) +static unsigned int selinux_ip_postroute(struct sk_buff *skb, int ifindex, + u16 family) { - return selinux_ip_postroute_last(hooknum, skb, in, out, okfn, PF_INET); + u32 secmark_perm; + u32 peer_sid; + struct sock *sk; + struct avc_audit_data ad; + char *addrp; + u8 proto; + u8 secmark_active; + u8 peerlbl_active; + + AVC_AUDIT_DATA_INIT(&ad, NET); + ad.u.net.netif = ifindex; + ad.u.net.family = family; + if (selinux_parse_skb(skb, &ad, &addrp, 0, &proto)) + return NF_DROP; + + /* If any sort of compatibility mode is enabled then handoff processing + * to the selinux_ip_postroute_compat() function to deal with the + * special handling. We do this in an attempt to keep this function + * as fast and as clean as possible. */ + if (selinux_compat_net || !selinux_policycap_netpeer) + return selinux_ip_postroute_compat(skb, ifindex, &ad, + family, addrp, proto); + + /* If skb->dst->xfrm is non-NULL then the packet is undergoing an IPsec + * packet transformation so allow the packet to pass without any checks + * since we'll have another chance to perform access control checks + * when the packet is on it's final way out. + * NOTE: there appear to be some IPv6 multicast cases where skb->dst + * is NULL, in this case go ahead and apply access control. */ + if (skb->dst != NULL && skb->dst->xfrm != NULL) + return NF_ACCEPT; + + secmark_active = selinux_secmark_enabled(); + peerlbl_active = netlbl_enabled() || selinux_xfrm_enabled(); + if (!secmark_active && !peerlbl_active) + return NF_ACCEPT; + + /* if the packet is locally generated (skb->sk != NULL) then use the + * socket's label as the peer label, otherwise the packet is being + * forwarded through this system and we need to fetch the peer label + * directly from the packet */ + sk = skb->sk; + if (sk) { + struct sk_security_struct *sksec = sk->sk_security; + peer_sid = sksec->sid; + secmark_perm = PACKET__SEND; + } else { + if (selinux_skb_peerlbl_sid(skb, family, &peer_sid)) + return NF_DROP; + secmark_perm = PACKET__FORWARD_OUT; + } + + if (secmark_active) + if (avc_has_perm(peer_sid, skb->secmark, + SECCLASS_PACKET, secmark_perm, &ad)) + return NF_DROP; + + if (peerlbl_active) { + u32 if_sid; + u32 node_sid; + + if (sel_netif_sid(ifindex, &if_sid)) + return NF_DROP; + if (avc_has_perm(peer_sid, if_sid, + SECCLASS_NETIF, NETIF__EGRESS, &ad)) + return NF_DROP; + + if (sel_netnode_sid(addrp, family, &node_sid)) + return NF_DROP; + if (avc_has_perm(peer_sid, node_sid, + SECCLASS_NODE, NODE__SENDTO, &ad)) + return NF_DROP; + } + + return NF_ACCEPT; +} + +static unsigned int selinux_ipv4_postroute(unsigned int hooknum, + struct sk_buff *skb, + const struct net_device *in, + const struct net_device *out, + int (*okfn)(struct sk_buff *)) +{ + return selinux_ip_postroute(skb, out->ifindex, PF_INET); } #if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE) - -static unsigned int selinux_ipv6_postroute_last(unsigned int hooknum, - struct sk_buff *skb, - const struct net_device *in, - const struct net_device *out, - int (*okfn)(struct sk_buff *)) +static unsigned int selinux_ipv6_postroute(unsigned int hooknum, + struct sk_buff *skb, + const struct net_device *in, + const struct net_device *out, + int (*okfn)(struct sk_buff *)) { - return selinux_ip_postroute_last(hooknum, skb, in, out, okfn, PF_INET6); + return selinux_ip_postroute(skb, out->ifindex, PF_INET6); } - #endif /* IPV6 */ #endif /* CONFIG_NETFILTER */ @@ -5277,22 +5483,40 @@ security_initcall(selinux_init); #if defined(CONFIG_NETFILTER) -static struct nf_hook_ops selinux_ipv4_op = { - .hook = selinux_ipv4_postroute_last, - .owner = THIS_MODULE, - .pf = PF_INET, - .hooknum = NF_INET_POST_ROUTING, - .priority = NF_IP_PRI_SELINUX_LAST, +static struct nf_hook_ops selinux_ipv4_ops[] = { + { + .hook = selinux_ipv4_postroute, + .owner = THIS_MODULE, + .pf = PF_INET, + .hooknum = NF_INET_POST_ROUTING, + .priority = NF_IP_PRI_SELINUX_LAST, + }, + { + .hook = selinux_ipv4_forward, + .owner = THIS_MODULE, + .pf = PF_INET, + .hooknum = NF_INET_FORWARD, + .priority = NF_IP_PRI_SELINUX_FIRST, + } }; #if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE) -static struct nf_hook_ops selinux_ipv6_op = { - .hook = selinux_ipv6_postroute_last, - .owner = THIS_MODULE, - .pf = PF_INET6, - .hooknum = NF_INET_POST_ROUTING, - .priority = NF_IP6_PRI_SELINUX_LAST, +static struct nf_hook_ops selinux_ipv6_ops[] = { + { + .hook = selinux_ipv6_postroute, + .owner = THIS_MODULE, + .pf = PF_INET6, + .hooknum = NF_INET_POST_ROUTING, + .priority = NF_IP6_PRI_SELINUX_LAST, + }, + { + .hook = selinux_ipv6_forward, + .owner = THIS_MODULE, + .pf = PF_INET6, + .hooknum = NF_INET_FORWARD, + .priority = NF_IP6_PRI_SELINUX_FIRST, + } }; #endif /* IPV6 */ @@ -5300,22 +5524,27 @@ static struct nf_hook_ops selinux_ipv6_op = { static int __init selinux_nf_ip_init(void) { int err = 0; + u32 iter; if (!selinux_enabled) goto out; printk(KERN_DEBUG "SELinux: Registering netfilter hooks\n"); - err = nf_register_hook(&selinux_ipv4_op); - if (err) - panic("SELinux: nf_register_hook for IPv4: error %d\n", err); + for (iter = 0; iter < ARRAY_SIZE(selinux_ipv4_ops); iter++) { + err = nf_register_hook(&selinux_ipv4_ops[iter]); + if (err) + panic("SELinux: nf_register_hook for IPv4: error %d\n", + err); + } #if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE) - - err = nf_register_hook(&selinux_ipv6_op); - if (err) - panic("SELinux: nf_register_hook for IPv6: error %d\n", err); - + for (iter = 0; iter < ARRAY_SIZE(selinux_ipv6_ops); iter++) { + err = nf_register_hook(&selinux_ipv6_ops[iter]); + if (err) + panic("SELinux: nf_register_hook for IPv6: error %d\n", + err); + } #endif /* IPV6 */ out: @@ -5327,11 +5556,15 @@ __initcall(selinux_nf_ip_init); #ifdef CONFIG_SECURITY_SELINUX_DISABLE static void selinux_nf_ip_exit(void) { + u32 iter; + printk(KERN_DEBUG "SELinux: Unregistering netfilter hooks\n"); - nf_unregister_hook(&selinux_ipv4_op); + for (iter = 0; iter < ARRAY_SIZE(selinux_ipv4_ops); iter++) + nf_unregister_hook(&selinux_ipv4_ops[iter]); #if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE) - nf_unregister_hook(&selinux_ipv6_op); + for (iter = 0; iter < ARRAY_SIZE(selinux_ipv6_ops); iter++) + nf_unregister_hook(&selinux_ipv6_ops[iter]); #endif /* IPV6 */ } #endif diff --git a/security/selinux/include/av_perm_to_string.h b/security/selinux/include/av_perm_to_string.h index 049bf69429b6..399f868c5c8f 100644 --- a/security/selinux/include/av_perm_to_string.h +++ b/security/selinux/include/av_perm_to_string.h @@ -37,6 +37,8 @@ S_(SECCLASS_NODE, NODE__ENFORCE_DEST, "enforce_dest") S_(SECCLASS_NODE, NODE__DCCP_RECV, "dccp_recv") S_(SECCLASS_NODE, NODE__DCCP_SEND, "dccp_send") + S_(SECCLASS_NODE, NODE__RECVFROM, "recvfrom") + S_(SECCLASS_NODE, NODE__SENDTO, "sendto") S_(SECCLASS_NETIF, NETIF__TCP_RECV, "tcp_recv") S_(SECCLASS_NETIF, NETIF__TCP_SEND, "tcp_send") S_(SECCLASS_NETIF, NETIF__UDP_RECV, "udp_recv") @@ -45,6 +47,8 @@ S_(SECCLASS_NETIF, NETIF__RAWIP_SEND, "rawip_send") S_(SECCLASS_NETIF, NETIF__DCCP_RECV, "dccp_recv") S_(SECCLASS_NETIF, NETIF__DCCP_SEND, "dccp_send") + S_(SECCLASS_NETIF, NETIF__INGRESS, "ingress") + S_(SECCLASS_NETIF, NETIF__EGRESS, "egress") S_(SECCLASS_UNIX_STREAM_SOCKET, UNIX_STREAM_SOCKET__CONNECTTO, "connectto") S_(SECCLASS_UNIX_STREAM_SOCKET, UNIX_STREAM_SOCKET__NEWCONN, "newconn") S_(SECCLASS_UNIX_STREAM_SOCKET, UNIX_STREAM_SOCKET__ACCEPTFROM, "acceptfrom") @@ -149,6 +153,10 @@ S_(SECCLASS_PACKET, PACKET__SEND, "send") S_(SECCLASS_PACKET, PACKET__RECV, "recv") S_(SECCLASS_PACKET, PACKET__RELABELTO, "relabelto") + S_(SECCLASS_PACKET, PACKET__FLOW_IN, "flow_in") + S_(SECCLASS_PACKET, PACKET__FLOW_OUT, "flow_out") + S_(SECCLASS_PACKET, PACKET__FORWARD_IN, "forward_in") + S_(SECCLASS_PACKET, PACKET__FORWARD_OUT, "forward_out") S_(SECCLASS_KEY, KEY__VIEW, "view") S_(SECCLASS_KEY, KEY__READ, "read") S_(SECCLASS_KEY, KEY__WRITE, "write") @@ -159,3 +167,4 @@ S_(SECCLASS_DCCP_SOCKET, DCCP_SOCKET__NODE_BIND, "node_bind") S_(SECCLASS_DCCP_SOCKET, DCCP_SOCKET__NAME_CONNECT, "name_connect") S_(SECCLASS_MEMPROTECT, MEMPROTECT__MMAP_ZERO, "mmap_zero") + S_(SECCLASS_PEER, PEER__RECV, "recv") diff --git a/security/selinux/include/av_permissions.h b/security/selinux/include/av_permissions.h index eda89a2ec635..84c9abc80978 100644 --- a/security/selinux/include/av_permissions.h +++ b/security/selinux/include/av_permissions.h @@ -292,6 +292,8 @@ #define NODE__ENFORCE_DEST 0x00000040UL #define NODE__DCCP_RECV 0x00000080UL #define NODE__DCCP_SEND 0x00000100UL +#define NODE__RECVFROM 0x00000200UL +#define NODE__SENDTO 0x00000400UL #define NETIF__TCP_RECV 0x00000001UL #define NETIF__TCP_SEND 0x00000002UL #define NETIF__UDP_RECV 0x00000004UL @@ -300,6 +302,8 @@ #define NETIF__RAWIP_SEND 0x00000020UL #define NETIF__DCCP_RECV 0x00000040UL #define NETIF__DCCP_SEND 0x00000080UL +#define NETIF__INGRESS 0x00000100UL +#define NETIF__EGRESS 0x00000200UL #define NETLINK_SOCKET__IOCTL 0x00000001UL #define NETLINK_SOCKET__READ 0x00000002UL #define NETLINK_SOCKET__WRITE 0x00000004UL @@ -792,6 +796,10 @@ #define PACKET__SEND 0x00000001UL #define PACKET__RECV 0x00000002UL #define PACKET__RELABELTO 0x00000004UL +#define PACKET__FLOW_IN 0x00000008UL +#define PACKET__FLOW_OUT 0x00000010UL +#define PACKET__FORWARD_IN 0x00000020UL +#define PACKET__FORWARD_OUT 0x00000040UL #define KEY__VIEW 0x00000001UL #define KEY__READ 0x00000002UL #define KEY__WRITE 0x00000004UL @@ -824,3 +832,4 @@ #define DCCP_SOCKET__NODE_BIND 0x00400000UL #define DCCP_SOCKET__NAME_CONNECT 0x00800000UL #define MEMPROTECT__MMAP_ZERO 0x00000001UL +#define PEER__RECV 0x00000001UL diff --git a/security/selinux/include/avc.h b/security/selinux/include/avc.h index 553607a19e92..80c28fa6621c 100644 --- a/security/selinux/include/avc.h +++ b/security/selinux/include/avc.h @@ -51,7 +51,7 @@ struct avc_audit_data { struct inode *inode; } fs; struct { - char *netif; + int netif; struct sock *sk; u16 family; __be16 dport; diff --git a/security/selinux/include/class_to_string.h b/security/selinux/include/class_to_string.h index e77de0e62ea0..b1b0d1d8f950 100644 --- a/security/selinux/include/class_to_string.h +++ b/security/selinux/include/class_to_string.h @@ -64,3 +64,10 @@ S_(NULL) S_("dccp_socket") S_("memprotect") + S_(NULL) + S_(NULL) + S_(NULL) + S_(NULL) + S_(NULL) + S_(NULL) + S_("peer") diff --git a/security/selinux/include/flask.h b/security/selinux/include/flask.h index a9c2b20f14b5..09e9dd23ee1a 100644 --- a/security/selinux/include/flask.h +++ b/security/selinux/include/flask.h @@ -50,6 +50,7 @@ #define SECCLASS_KEY 58 #define SECCLASS_DCCP_SOCKET 60 #define SECCLASS_MEMPROTECT 61 +#define SECCLASS_PEER 68 /* * Security identifier indices for initial entities diff --git a/security/selinux/include/netif.h b/security/selinux/include/netif.h index 8bd6f9992d2b..ce23edd128b3 100644 --- a/security/selinux/include/netif.h +++ b/security/selinux/include/netif.h @@ -7,6 +7,8 @@ * Author: James Morris * * Copyright (C) 2003 Red Hat, Inc., James Morris + * Copyright (C) 2007 Hewlett-Packard Development Company, L.P. + * Paul Moore, * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2, @@ -15,7 +17,7 @@ #ifndef _SELINUX_NETIF_H_ #define _SELINUX_NETIF_H_ -int sel_netif_sids(struct net_device *dev, u32 *if_sid, u32 *msg_sid); +int sel_netif_sid(int ifindex, u32 *sid); #endif /* _SELINUX_NETIF_H_ */ diff --git a/security/selinux/include/netlabel.h b/security/selinux/include/netlabel.h index 218e3f77c350..00a2809c8506 100644 --- a/security/selinux/include/netlabel.h +++ b/security/selinux/include/netlabel.h @@ -46,13 +46,17 @@ void selinux_netlbl_sk_security_init(struct sk_security_struct *ssec, void selinux_netlbl_sk_security_clone(struct sk_security_struct *ssec, struct sk_security_struct *newssec); -int selinux_netlbl_skbuff_getsid(struct sk_buff *skb, u32 base_sid, u32 *sid); +int selinux_netlbl_skbuff_getsid(struct sk_buff *skb, + u16 family, + u32 *type, + u32 *sid); void selinux_netlbl_sock_graft(struct sock *sk, struct socket *sock); int selinux_netlbl_socket_post_create(struct socket *sock); int selinux_netlbl_inode_permission(struct inode *inode, int mask); int selinux_netlbl_sock_rcv_skb(struct sk_security_struct *sksec, struct sk_buff *skb, + u16 family, struct avc_audit_data *ad); int selinux_netlbl_socket_setsockopt(struct socket *sock, int level, @@ -83,9 +87,11 @@ static inline void selinux_netlbl_sk_security_clone( } static inline int selinux_netlbl_skbuff_getsid(struct sk_buff *skb, - u32 base_sid, + u16 family, + u32 *type, u32 *sid) { + *type = NETLBL_NLTYPE_NONE; *sid = SECSID_NULL; return 0; } @@ -106,6 +112,7 @@ static inline int selinux_netlbl_inode_permission(struct inode *inode, } static inline int selinux_netlbl_sock_rcv_skb(struct sk_security_struct *sksec, struct sk_buff *skb, + u16 family, struct avc_audit_data *ad) { return 0; diff --git a/security/selinux/include/netnode.h b/security/selinux/include/netnode.h new file mode 100644 index 000000000000..1b94450d11d2 --- /dev/null +++ b/security/selinux/include/netnode.h @@ -0,0 +1,32 @@ +/* + * Network node table + * + * SELinux must keep a mapping of network nodes to labels/SIDs. This + * mapping is maintained as part of the normal policy but a fast cache is + * needed to reduce the lookup overhead since most of these queries happen on + * a per-packet basis. + * + * Author: Paul Moore + * + */ + +/* + * (c) Copyright Hewlett-Packard Development Company, L.P., 2007 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * 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. + * + */ + +#ifndef _SELINUX_NETNODE_H +#define _SELINUX_NETNODE_H + +int sel_netnode_sid(void *addr, u16 family, u32 *sid); + +#endif diff --git a/security/selinux/include/objsec.h b/security/selinux/include/objsec.h index 4138a80f8e27..c6c2bb4ebacc 100644 --- a/security/selinux/include/objsec.h +++ b/security/selinux/include/objsec.h @@ -96,17 +96,25 @@ struct bprm_security_struct { }; struct netif_security_struct { - struct net_device *dev; /* back pointer */ - u32 if_sid; /* SID for this interface */ - u32 msg_sid; /* default SID for messages received on this interface */ + int ifindex; /* device index */ + u32 sid; /* SID for this interface */ +}; + +struct netnode_security_struct { + union { + __be32 ipv4; /* IPv4 node address */ + struct in6_addr ipv6; /* IPv6 node address */ + } addr; + u32 sid; /* SID for this node */ + u16 family; /* address family */ }; struct sk_security_struct { struct sock *sk; /* back pointer to sk object */ u32 sid; /* SID of this object */ u32 peer_sid; /* SID of peer */ -#ifdef CONFIG_NETLABEL u16 sclass; /* sock security class */ +#ifdef CONFIG_NETLABEL enum { /* NetLabel state */ NLBL_UNSET = 0, NLBL_REQUIRE, diff --git a/security/selinux/include/security.h b/security/selinux/include/security.h index 39337afffec2..23137c17f917 100644 --- a/security/selinux/include/security.h +++ b/security/selinux/include/security.h @@ -25,13 +25,14 @@ #define POLICYDB_VERSION_MLS 19 #define POLICYDB_VERSION_AVTAB 20 #define POLICYDB_VERSION_RANGETRANS 21 +#define POLICYDB_VERSION_POLCAP 22 /* Range of policy versions we understand*/ #define POLICYDB_VERSION_MIN POLICYDB_VERSION_BASE #ifdef CONFIG_SECURITY_SELINUX_POLICYDB_VERSION_MAX #define POLICYDB_VERSION_MAX CONFIG_SECURITY_SELINUX_POLICYDB_VERSION_MAX_VALUE #else -#define POLICYDB_VERSION_MAX POLICYDB_VERSION_RANGETRANS +#define POLICYDB_VERSION_MAX POLICYDB_VERSION_POLCAP #endif struct netlbl_lsm_secattr; @@ -39,8 +40,19 @@ struct netlbl_lsm_secattr; extern int selinux_enabled; extern int selinux_mls_enabled; +/* Policy capabilities */ +enum { + POLICYDB_CAPABILITY_NETPEER, + __POLICYDB_CAPABILITY_MAX +}; +#define POLICYDB_CAPABILITY_MAX (__POLICYDB_CAPABILITY_MAX - 1) + +extern int selinux_policycap_netpeer; + int security_load_policy(void * data, size_t len); +int security_policycap_supported(unsigned int req_cap); + #define SEL_VEC_MAX 32 struct av_decision { u32 allowed; @@ -77,8 +89,7 @@ int security_get_user_sids(u32 callsid, char *username, int security_port_sid(u16 domain, u16 type, u8 protocol, u16 port, u32 *out_sid); -int security_netif_sid(char *name, u32 *if_sid, - u32 *msg_sid); +int security_netif_sid(char *name, u32 *if_sid); int security_node_sid(u16 domain, void *addr, u32 addrlen, u32 *out_sid); @@ -88,10 +99,15 @@ int security_validate_transition(u32 oldsid, u32 newsid, u32 tasksid, int security_sid_mls_copy(u32 sid, u32 mls_sid, u32 *new_sid); +int security_net_peersid_resolve(u32 nlbl_sid, u32 nlbl_type, + u32 xfrm_sid, + u32 *peer_sid); + int security_get_classes(char ***classes, int *nclasses); int security_get_permissions(char *class, char ***perms, int *nperms); int security_get_reject_unknown(void); int security_get_allow_unknown(void); +int security_get_policycaps(int *len, int **values); #define SECURITY_FS_USE_XATTR 1 /* use xattr */ #define SECURITY_FS_USE_TRANS 2 /* use transition SIDs, e.g. devpts/tmpfs */ @@ -108,7 +124,6 @@ int security_genfs_sid(const char *fstype, char *name, u16 sclass, #ifdef CONFIG_NETLABEL int security_netlbl_secattr_to_sid(struct netlbl_lsm_secattr *secattr, - u32 base_sid, u32 *sid); int security_netlbl_sid_to_secattr(u32 sid, @@ -116,7 +131,6 @@ int security_netlbl_sid_to_secattr(u32 sid, #else static inline int security_netlbl_secattr_to_sid( struct netlbl_lsm_secattr *secattr, - u32 base_sid, u32 *sid) { return -EIDRM; diff --git a/security/selinux/include/xfrm.h b/security/selinux/include/xfrm.h index 31929e39f5ca..36b0510efa7b 100644 --- a/security/selinux/include/xfrm.h +++ b/security/selinux/include/xfrm.h @@ -32,6 +32,13 @@ static inline struct inode_security_struct *get_sock_isec(struct sock *sk) } #ifdef CONFIG_SECURITY_NETWORK_XFRM +extern atomic_t selinux_xfrm_refcount; + +static inline int selinux_xfrm_enabled(void) +{ + return (atomic_read(&selinux_xfrm_refcount) > 0); +} + int selinux_xfrm_sock_rcv_skb(u32 sid, struct sk_buff *skb, struct avc_audit_data *ad); int selinux_xfrm_postroute_last(u32 isec_sid, struct sk_buff *skb, @@ -43,6 +50,11 @@ static inline void selinux_xfrm_notify_policyload(void) atomic_inc(&flow_cache_genid); } #else +static inline int selinux_xfrm_enabled(void) +{ + return 0; +} + static inline int selinux_xfrm_sock_rcv_skb(u32 isec_sid, struct sk_buff *skb, struct avc_audit_data *ad) { diff --git a/security/selinux/netif.c b/security/selinux/netif.c index e87ab948104c..013d3117a86b 100644 --- a/security/selinux/netif.c +++ b/security/selinux/netif.c @@ -7,6 +7,8 @@ * Author: James Morris * * Copyright (C) 2003 Red Hat, Inc., James Morris + * Copyright (C) 2007 Hewlett-Packard Development Company, L.P. + * Paul Moore * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2, @@ -29,14 +31,6 @@ #define SEL_NETIF_HASH_SIZE 64 #define SEL_NETIF_HASH_MAX 1024 -#undef DEBUG - -#ifdef DEBUG -#define DEBUGP printk -#else -#define DEBUGP(format, args...) -#endif - struct sel_netif { struct list_head list; @@ -49,174 +43,226 @@ static LIST_HEAD(sel_netif_list); static DEFINE_SPINLOCK(sel_netif_lock); static struct list_head sel_netif_hash[SEL_NETIF_HASH_SIZE]; -static inline u32 sel_netif_hasfn(struct net_device *dev) +/** + * sel_netif_hashfn - Hashing function for the interface table + * @ifindex: the network interface + * + * Description: + * This is the hashing function for the network interface table, it returns the + * bucket number for the given interface. + * + */ +static inline u32 sel_netif_hashfn(int ifindex) { - return (dev->ifindex & (SEL_NETIF_HASH_SIZE - 1)); + return (ifindex & (SEL_NETIF_HASH_SIZE - 1)); } -/* - * All of the devices should normally fit in the hash, so we optimize - * for that case. +/** + * sel_netif_find - Search for an interface record + * @ifindex: the network interface + * + * Description: + * Search the network interface table and return the record matching @ifindex. + * If an entry can not be found in the table return NULL. + * */ -static inline struct sel_netif *sel_netif_find(struct net_device *dev) +static inline struct sel_netif *sel_netif_find(int ifindex) { - struct list_head *pos; - int idx = sel_netif_hasfn(dev); + int idx = sel_netif_hashfn(ifindex); + struct sel_netif *netif; - __list_for_each_rcu(pos, &sel_netif_hash[idx]) { - struct sel_netif *netif = list_entry(pos, - struct sel_netif, list); - if (likely(netif->nsec.dev == dev)) + list_for_each_entry_rcu(netif, &sel_netif_hash[idx], list) + /* all of the devices should normally fit in the hash, so we + * optimize for that case */ + if (likely(netif->nsec.ifindex == ifindex)) return netif; - } + return NULL; } +/** + * sel_netif_insert - Insert a new interface into the table + * @netif: the new interface record + * + * Description: + * Add a new interface record to the network interface hash table. Returns + * zero on success, negative values on failure. + * + */ static int sel_netif_insert(struct sel_netif *netif) { - int idx, ret = 0; + int idx; - if (sel_netif_total >= SEL_NETIF_HASH_MAX) { - ret = -ENOSPC; - goto out; - } + if (sel_netif_total >= SEL_NETIF_HASH_MAX) + return -ENOSPC; - idx = sel_netif_hasfn(netif->nsec.dev); + idx = sel_netif_hashfn(netif->nsec.ifindex); list_add_rcu(&netif->list, &sel_netif_hash[idx]); sel_netif_total++; -out: - return ret; + + return 0; } +/** + * sel_netif_free - Frees an interface entry + * @p: the entry's RCU field + * + * Description: + * This function is designed to be used as a callback to the call_rcu() + * function so that memory allocated to a hash table interface entry can be + * released safely. + * + */ static void sel_netif_free(struct rcu_head *p) { struct sel_netif *netif = container_of(p, struct sel_netif, rcu_head); - - DEBUGP("%s: %s\n", __FUNCTION__, netif->nsec.dev->name); kfree(netif); } +/** + * sel_netif_destroy - Remove an interface record from the table + * @netif: the existing interface record + * + * Description: + * Remove an existing interface record from the network interface table. + * + */ static void sel_netif_destroy(struct sel_netif *netif) { - DEBUGP("%s: %s\n", __FUNCTION__, netif->nsec.dev->name); - list_del_rcu(&netif->list); sel_netif_total--; call_rcu(&netif->rcu_head, sel_netif_free); } -static struct sel_netif *sel_netif_lookup(struct net_device *dev) +/** + * sel_netif_sid_slow - Lookup the SID of a network interface using the policy + * @ifindex: the network interface + * @sid: interface SID + * + * Description: + * This function determines the SID of a network interface by quering the + * security policy. The result is added to the network interface table to + * speedup future queries. Returns zero on success, negative values on + * failure. + * + */ +static int sel_netif_sid_slow(int ifindex, u32 *sid) { int ret; - struct sel_netif *netif, *new; - struct netif_security_struct *nsec; + struct sel_netif *netif; + struct sel_netif *new = NULL; + struct net_device *dev; - netif = sel_netif_find(dev); - if (likely(netif != NULL)) - goto out; - - new = kzalloc(sizeof(*new), GFP_ATOMIC); - if (!new) { - netif = ERR_PTR(-ENOMEM); - goto out; - } - - nsec = &new->nsec; + /* NOTE: we always use init's network namespace since we don't + * currently support containers */ - ret = security_netif_sid(dev->name, &nsec->if_sid, &nsec->msg_sid); - if (ret < 0) { - kfree(new); - netif = ERR_PTR(ret); - goto out; + dev = dev_get_by_index(&init_net, ifindex); + if (unlikely(dev == NULL)) { + printk(KERN_WARNING + "SELinux: failure in sel_netif_sid_slow()," + " invalid network interface (%d)\n", ifindex); + return -ENOENT; } - nsec->dev = dev; - spin_lock_bh(&sel_netif_lock); - - netif = sel_netif_find(dev); - if (netif) { - spin_unlock_bh(&sel_netif_lock); - kfree(new); + netif = sel_netif_find(ifindex); + if (netif != NULL) { + *sid = netif->nsec.sid; + ret = 0; goto out; } - + new = kzalloc(sizeof(*new), GFP_ATOMIC); + if (new == NULL) { + ret = -ENOMEM; + goto out; + } + ret = security_netif_sid(dev->name, &new->nsec.sid); + if (ret != 0) + goto out; + new->nsec.ifindex = ifindex; ret = sel_netif_insert(new); - spin_unlock_bh(&sel_netif_lock); - - if (ret) { - kfree(new); - netif = ERR_PTR(ret); + if (ret != 0) goto out; - } + *sid = new->nsec.sid; - netif = new; - - DEBUGP("new: ifindex=%u name=%s if_sid=%u msg_sid=%u\n", dev->ifindex, dev->name, - nsec->if_sid, nsec->msg_sid); out: - return netif; -} - -static void sel_netif_assign_sids(u32 if_sid_in, u32 msg_sid_in, u32 *if_sid_out, u32 *msg_sid_out) -{ - if (if_sid_out) - *if_sid_out = if_sid_in; - if (msg_sid_out) - *msg_sid_out = msg_sid_in; -} - -static int sel_netif_sids_slow(struct net_device *dev, u32 *if_sid, u32 *msg_sid) -{ - int ret = 0; - u32 tmp_if_sid, tmp_msg_sid; - - ret = security_netif_sid(dev->name, &tmp_if_sid, &tmp_msg_sid); - if (!ret) - sel_netif_assign_sids(tmp_if_sid, tmp_msg_sid, if_sid, msg_sid); + spin_unlock_bh(&sel_netif_lock); + dev_put(dev); + if (unlikely(ret)) { + printk(KERN_WARNING + "SELinux: failure in sel_netif_sid_slow()," + " unable to determine network interface label (%d)\n", + ifindex); + kfree(new); + } return ret; } -int sel_netif_sids(struct net_device *dev, u32 *if_sid, u32 *msg_sid) +/** + * sel_netif_sid - Lookup the SID of a network interface + * @ifindex: the network interface + * @sid: interface SID + * + * Description: + * This function determines the SID of a network interface using the fastest + * method possible. First the interface table is queried, but if an entry + * can't be found then the policy is queried and the result is added to the + * table to speedup future queries. Returns zero on success, negative values + * on failure. + * + */ +int sel_netif_sid(int ifindex, u32 *sid) { - int ret = 0; struct sel_netif *netif; rcu_read_lock(); - netif = sel_netif_lookup(dev); - if (IS_ERR(netif)) { + netif = sel_netif_find(ifindex); + if (likely(netif != NULL)) { + *sid = netif->nsec.sid; rcu_read_unlock(); - ret = sel_netif_sids_slow(dev, if_sid, msg_sid); - goto out; + return 0; } - sel_netif_assign_sids(netif->nsec.if_sid, netif->nsec.msg_sid, if_sid, msg_sid); rcu_read_unlock(); -out: - return ret; + + return sel_netif_sid_slow(ifindex, sid); } -static void sel_netif_kill(struct net_device *dev) +/** + * sel_netif_kill - Remove an entry from the network interface table + * @ifindex: the network interface + * + * Description: + * This function removes the entry matching @ifindex from the network interface + * table if it exists. + * + */ +static void sel_netif_kill(int ifindex) { struct sel_netif *netif; spin_lock_bh(&sel_netif_lock); - netif = sel_netif_find(dev); + netif = sel_netif_find(ifindex); if (netif) sel_netif_destroy(netif); spin_unlock_bh(&sel_netif_lock); } +/** + * sel_netif_flush - Flush the entire network interface table + * + * Description: + * Remove all entries from the network interface table. + * + */ static void sel_netif_flush(void) { int idx; + struct sel_netif *netif; spin_lock_bh(&sel_netif_lock); - for (idx = 0; idx < SEL_NETIF_HASH_SIZE; idx++) { - struct sel_netif *netif; - + for (idx = 0; idx < SEL_NETIF_HASH_SIZE; idx++) list_for_each_entry(netif, &sel_netif_hash[idx], list) sel_netif_destroy(netif); - } spin_unlock_bh(&sel_netif_lock); } @@ -239,7 +285,7 @@ static int sel_netif_netdev_notifier_handler(struct notifier_block *this, return NOTIFY_DONE; if (event == NETDEV_DOWN) - sel_netif_kill(dev); + sel_netif_kill(dev->ifindex); return NOTIFY_DONE; } @@ -250,10 +296,10 @@ static struct notifier_block sel_netif_netdev_notifier = { static __init int sel_netif_init(void) { - int i, err = 0; + int i, err; if (!selinux_enabled) - goto out; + return 0; for (i = 0; i < SEL_NETIF_HASH_SIZE; i++) INIT_LIST_HEAD(&sel_netif_hash[i]); @@ -265,7 +311,6 @@ static __init int sel_netif_init(void) if (err) panic("avc_add_callback() failed, error %d\n", err); -out: return err; } diff --git a/security/selinux/netlabel.c b/security/selinux/netlabel.c index 66e013d6f6f6..0fa2be4149e8 100644 --- a/security/selinux/netlabel.c +++ b/security/selinux/netlabel.c @@ -35,6 +35,33 @@ #include "objsec.h" #include "security.h" +/** + * selinux_netlbl_sidlookup_cached - Cache a SID lookup + * @skb: the packet + * @secattr: the NetLabel security attributes + * @sid: the SID + * + * Description: + * Query the SELinux security server to lookup the correct SID for the given + * security attributes. If the query is successful, cache the result to speed + * up future lookups. Returns zero on success, negative values on failure. + * + */ +static int selinux_netlbl_sidlookup_cached(struct sk_buff *skb, + struct netlbl_lsm_secattr *secattr, + u32 *sid) +{ + int rc; + + rc = security_netlbl_secattr_to_sid(secattr, sid); + if (rc == 0 && + (secattr->flags & NETLBL_SECATTR_CACHEABLE) && + (secattr->flags & NETLBL_SECATTR_CACHE)) + netlbl_cache_add(skb, secattr); + + return rc; +} + /** * selinux_netlbl_sock_setsid - Label a socket using the NetLabel mechanism * @sk: the socket to label @@ -137,14 +164,14 @@ void selinux_netlbl_sk_security_clone(struct sk_security_struct *ssec, * lock as other threads could have access to ssec */ rcu_read_lock(); selinux_netlbl_sk_security_reset(newssec, ssec->sk->sk_family); - newssec->sclass = ssec->sclass; rcu_read_unlock(); } /** * selinux_netlbl_skbuff_getsid - Get the sid of a packet using NetLabel * @skb: the packet - * @base_sid: the SELinux SID to use as a context for MLS only attributes + * @family: protocol family + * @type: NetLabel labeling protocol type * @sid: the SID * * Description: @@ -153,7 +180,10 @@ void selinux_netlbl_sk_security_clone(struct sk_security_struct *ssec, * assign to the packet. Returns zero on success, negative values on failure. * */ -int selinux_netlbl_skbuff_getsid(struct sk_buff *skb, u32 base_sid, u32 *sid) +int selinux_netlbl_skbuff_getsid(struct sk_buff *skb, + u16 family, + u32 *type, + u32 *sid) { int rc; struct netlbl_lsm_secattr secattr; @@ -164,15 +194,12 @@ int selinux_netlbl_skbuff_getsid(struct sk_buff *skb, u32 base_sid, u32 *sid) } netlbl_secattr_init(&secattr); - rc = netlbl_skbuff_getattr(skb, &secattr); - if (rc == 0 && secattr.flags != NETLBL_SECATTR_NONE) { - rc = security_netlbl_secattr_to_sid(&secattr, base_sid, sid); - if (rc == 0 && - (secattr.flags & NETLBL_SECATTR_CACHEABLE) && - (secattr.flags & NETLBL_SECATTR_CACHE)) - netlbl_cache_add(skb, &secattr); - } else + rc = netlbl_skbuff_getattr(skb, family, &secattr); + if (rc == 0 && secattr.flags != NETLBL_SECATTR_NONE) + rc = selinux_netlbl_sidlookup_cached(skb, &secattr, sid); + else *sid = SECSID_NULL; + *type = secattr.type; netlbl_secattr_destroy(&secattr); return rc; @@ -190,13 +217,10 @@ int selinux_netlbl_skbuff_getsid(struct sk_buff *skb, u32 base_sid, u32 *sid) */ void selinux_netlbl_sock_graft(struct sock *sk, struct socket *sock) { - struct inode_security_struct *isec = SOCK_INODE(sock)->i_security; struct sk_security_struct *sksec = sk->sk_security; struct netlbl_lsm_secattr secattr; u32 nlbl_peer_sid; - sksec->sclass = isec->sclass; - rcu_read_lock(); if (sksec->nlbl_state != NLBL_REQUIRE) { @@ -207,9 +231,7 @@ void selinux_netlbl_sock_graft(struct sock *sk, struct socket *sock) netlbl_secattr_init(&secattr); if (netlbl_sock_getattr(sk, &secattr) == 0 && secattr.flags != NETLBL_SECATTR_NONE && - security_netlbl_secattr_to_sid(&secattr, - SECINITSID_NETMSG, - &nlbl_peer_sid) == 0) + security_netlbl_secattr_to_sid(&secattr, &nlbl_peer_sid) == 0) sksec->peer_sid = nlbl_peer_sid; netlbl_secattr_destroy(&secattr); @@ -234,11 +256,8 @@ int selinux_netlbl_socket_post_create(struct socket *sock) { int rc = 0; struct sock *sk = sock->sk; - struct inode_security_struct *isec = SOCK_INODE(sock)->i_security; struct sk_security_struct *sksec = sk->sk_security; - sksec->sclass = isec->sclass; - rcu_read_lock(); if (sksec->nlbl_state == NLBL_REQUIRE) rc = selinux_netlbl_sock_setsid(sk, sksec->sid); @@ -292,6 +311,7 @@ int selinux_netlbl_inode_permission(struct inode *inode, int mask) * selinux_netlbl_sock_rcv_skb - Do an inbound access check using NetLabel * @sksec: the sock's sk_security_struct * @skb: the packet + * @family: protocol family * @ad: the audit data * * Description: @@ -302,6 +322,7 @@ int selinux_netlbl_inode_permission(struct inode *inode, int mask) */ int selinux_netlbl_sock_rcv_skb(struct sk_security_struct *sksec, struct sk_buff *skb, + u16 family, struct avc_audit_data *ad) { int rc; @@ -313,16 +334,10 @@ int selinux_netlbl_sock_rcv_skb(struct sk_security_struct *sksec, return 0; netlbl_secattr_init(&secattr); - rc = netlbl_skbuff_getattr(skb, &secattr); - if (rc == 0 && secattr.flags != NETLBL_SECATTR_NONE) { - rc = security_netlbl_secattr_to_sid(&secattr, - SECINITSID_NETMSG, - &nlbl_sid); - if (rc == 0 && - (secattr.flags & NETLBL_SECATTR_CACHEABLE) && - (secattr.flags & NETLBL_SECATTR_CACHE)) - netlbl_cache_add(skb, &secattr); - } else + rc = netlbl_skbuff_getattr(skb, family, &secattr); + if (rc == 0 && secattr.flags != NETLBL_SECATTR_NONE) + rc = selinux_netlbl_sidlookup_cached(skb, &secattr, &nlbl_sid); + else nlbl_sid = SECINITSID_UNLABELED; netlbl_secattr_destroy(&secattr); if (rc != 0) diff --git a/security/selinux/netnode.c b/security/selinux/netnode.c new file mode 100644 index 000000000000..f3c526f2cacb --- /dev/null +++ b/security/selinux/netnode.c @@ -0,0 +1,354 @@ +/* + * Network node table + * + * SELinux must keep a mapping of network nodes to labels/SIDs. This + * mapping is maintained as part of the normal policy but a fast cache is + * needed to reduce the lookup overhead since most of these queries happen on + * a per-packet basis. + * + * Author: Paul Moore + * + * This code is heavily based on the "netif" concept originally developed by + * James Morris + * (see security/selinux/netif.c for more information) + * + */ + +/* + * (c) Copyright Hewlett-Packard Development Company, L.P., 2007 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * 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. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "objsec.h" + +#define SEL_NETNODE_HASH_SIZE 256 +#define SEL_NETNODE_HASH_BKT_LIMIT 16 + +struct sel_netnode { + struct netnode_security_struct nsec; + + struct list_head list; + struct rcu_head rcu; +}; + +/* NOTE: we are using a combined hash table for both IPv4 and IPv6, the reason + * for this is that I suspect most users will not make heavy use of both + * address families at the same time so one table will usually end up wasted, + * if this becomes a problem we can always add a hash table for each address + * family later */ + +static LIST_HEAD(sel_netnode_list); +static DEFINE_SPINLOCK(sel_netnode_lock); +static struct list_head sel_netnode_hash[SEL_NETNODE_HASH_SIZE]; + +/** + * sel_netnode_free - Frees a node entry + * @p: the entry's RCU field + * + * Description: + * This function is designed to be used as a callback to the call_rcu() + * function so that memory allocated to a hash table node entry can be + * released safely. + * + */ +static void sel_netnode_free(struct rcu_head *p) +{ + struct sel_netnode *node = container_of(p, struct sel_netnode, rcu); + kfree(node); +} + +/** + * sel_netnode_hashfn_ipv4 - IPv4 hashing function for the node table + * @addr: IPv4 address + * + * Description: + * This is the IPv4 hashing function for the node interface table, it returns + * the bucket number for the given IP address. + * + */ +static u32 sel_netnode_hashfn_ipv4(__be32 addr) +{ + /* at some point we should determine if the mismatch in byte order + * affects the hash function dramatically */ + return (addr & (SEL_NETNODE_HASH_SIZE - 1)); +} + +/** + * sel_netnode_hashfn_ipv6 - IPv6 hashing function for the node table + * @addr: IPv6 address + * + * Description: + * This is the IPv6 hashing function for the node interface table, it returns + * the bucket number for the given IP address. + * + */ +static u32 sel_netnode_hashfn_ipv6(const struct in6_addr *addr) +{ + /* just hash the least significant 32 bits to keep things fast (they + * are the most likely to be different anyway), we can revisit this + * later if needed */ + return (addr->s6_addr32[3] & (SEL_NETNODE_HASH_SIZE - 1)); +} + +/** + * sel_netnode_find - Search for a node record + * @addr: IP address + * @family: address family + * + * Description: + * Search the network node table and return the record matching @addr. If an + * entry can not be found in the table return NULL. + * + */ +static struct sel_netnode *sel_netnode_find(const void *addr, u16 family) +{ + u32 idx; + struct sel_netnode *node; + + switch (family) { + case PF_INET: + idx = sel_netnode_hashfn_ipv4(*(__be32 *)addr); + break; + case PF_INET6: + idx = sel_netnode_hashfn_ipv6(addr); + break; + default: + BUG(); + } + + list_for_each_entry_rcu(node, &sel_netnode_hash[idx], list) + if (node->nsec.family == family) + switch (family) { + case PF_INET: + if (node->nsec.addr.ipv4 == *(__be32 *)addr) + return node; + break; + case PF_INET6: + if (ipv6_addr_equal(&node->nsec.addr.ipv6, + addr)) + return node; + break; + } + + return NULL; +} + +/** + * sel_netnode_insert - Insert a new node into the table + * @node: the new node record + * + * Description: + * Add a new node record to the network address hash table. Returns zero on + * success, negative values on failure. + * + */ +static int sel_netnode_insert(struct sel_netnode *node) +{ + u32 idx; + u32 count = 0; + struct sel_netnode *iter; + + switch (node->nsec.family) { + case PF_INET: + idx = sel_netnode_hashfn_ipv4(node->nsec.addr.ipv4); + break; + case PF_INET6: + idx = sel_netnode_hashfn_ipv6(&node->nsec.addr.ipv6); + break; + default: + BUG(); + } + list_add_rcu(&node->list, &sel_netnode_hash[idx]); + + /* we need to impose a limit on the growth of the hash table so check + * this bucket to make sure it is within the specified bounds */ + list_for_each_entry(iter, &sel_netnode_hash[idx], list) + if (++count > SEL_NETNODE_HASH_BKT_LIMIT) { + list_del_rcu(&iter->list); + call_rcu(&iter->rcu, sel_netnode_free); + break; + } + + return 0; +} + +/** + * sel_netnode_destroy - Remove a node record from the table + * @node: the existing node record + * + * Description: + * Remove an existing node record from the network address table. + * + */ +static void sel_netnode_destroy(struct sel_netnode *node) +{ + list_del_rcu(&node->list); + call_rcu(&node->rcu, sel_netnode_free); +} + +/** + * sel_netnode_sid_slow - Lookup the SID of a network address using the policy + * @addr: the IP address + * @family: the address family + * @sid: node SID + * + * Description: + * This function determines the SID of a network address by quering the + * security policy. The result is added to the network address table to + * speedup future queries. Returns zero on success, negative values on + * failure. + * + */ +static int sel_netnode_sid_slow(void *addr, u16 family, u32 *sid) +{ + int ret; + struct sel_netnode *node; + struct sel_netnode *new = NULL; + + spin_lock_bh(&sel_netnode_lock); + node = sel_netnode_find(addr, family); + if (node != NULL) { + *sid = node->nsec.sid; + ret = 0; + goto out; + } + new = kzalloc(sizeof(*new), GFP_ATOMIC); + if (new == NULL) { + ret = -ENOMEM; + goto out; + } + switch (family) { + case PF_INET: + ret = security_node_sid(PF_INET, + addr, sizeof(struct in_addr), + &new->nsec.sid); + new->nsec.addr.ipv4 = *(__be32 *)addr; + break; + case PF_INET6: + ret = security_node_sid(PF_INET6, + addr, sizeof(struct in6_addr), + &new->nsec.sid); + ipv6_addr_copy(&new->nsec.addr.ipv6, addr); + break; + default: + BUG(); + } + if (ret != 0) + goto out; + new->nsec.family = family; + ret = sel_netnode_insert(new); + if (ret != 0) + goto out; + *sid = new->nsec.sid; + +out: + spin_unlock_bh(&sel_netnode_lock); + if (unlikely(ret)) { + printk(KERN_WARNING + "SELinux: failure in sel_netnode_sid_slow()," + " unable to determine network node label\n"); + kfree(new); + } + return ret; +} + +/** + * sel_netnode_sid - Lookup the SID of a network address + * @addr: the IP address + * @family: the address family + * @sid: node SID + * + * Description: + * This function determines the SID of a network address using the fastest + * method possible. First the address table is queried, but if an entry + * can't be found then the policy is queried and the result is added to the + * table to speedup future queries. Returns zero on success, negative values + * on failure. + * + */ +int sel_netnode_sid(void *addr, u16 family, u32 *sid) +{ + struct sel_netnode *node; + + rcu_read_lock(); + node = sel_netnode_find(addr, family); + if (node != NULL) { + *sid = node->nsec.sid; + rcu_read_unlock(); + return 0; + } + rcu_read_unlock(); + + return sel_netnode_sid_slow(addr, family, sid); +} + +/** + * sel_netnode_flush - Flush the entire network address table + * + * Description: + * Remove all entries from the network address table. + * + */ +static void sel_netnode_flush(void) +{ + u32 idx; + struct sel_netnode *node; + + spin_lock_bh(&sel_netnode_lock); + for (idx = 0; idx < SEL_NETNODE_HASH_SIZE; idx++) + list_for_each_entry(node, &sel_netnode_hash[idx], list) + sel_netnode_destroy(node); + spin_unlock_bh(&sel_netnode_lock); +} + +static int sel_netnode_avc_callback(u32 event, u32 ssid, u32 tsid, + u16 class, u32 perms, u32 *retained) +{ + if (event == AVC_CALLBACK_RESET) { + sel_netnode_flush(); + synchronize_net(); + } + return 0; +} + +static __init int sel_netnode_init(void) +{ + int iter; + int ret; + + if (!selinux_enabled) + return 0; + + for (iter = 0; iter < SEL_NETNODE_HASH_SIZE; iter++) + INIT_LIST_HEAD(&sel_netnode_hash[iter]); + + ret = avc_add_callback(sel_netnode_avc_callback, AVC_CALLBACK_RESET, + SECSID_NULL, SECSID_NULL, SECCLASS_NULL, 0); + if (ret != 0) + panic("avc_add_callback() failed, error %d\n", ret); + + return ret; +} + +__initcall(sel_netnode_init); diff --git a/security/selinux/selinuxfs.c b/security/selinux/selinuxfs.c index 397fd4955fe1..a85740530afc 100644 --- a/security/selinux/selinuxfs.c +++ b/security/selinux/selinuxfs.c @@ -2,6 +2,11 @@ * * Added conditional policy language extensions * + * Updated: Hewlett-Packard + * + * Added support for the policy capability bitmap + * + * Copyright (C) 2007 Hewlett-Packard Development Company, L.P. * Copyright (C) 2003 - 2004 Tresys Technology, LLC * Copyright (C) 2004 Red Hat, Inc., James Morris * This program is free software; you can redistribute it and/or modify @@ -35,6 +40,11 @@ #include "objsec.h" #include "conditional.h" +/* Policy capability filenames */ +static char *policycap_names[] = { + "network_peer_controls" +}; + unsigned int selinux_checkreqprot = CONFIG_SECURITY_SELINUX_CHECKREQPROT_VALUE; #ifdef CONFIG_SECURITY_SELINUX_ENABLE_SECMARK_DEFAULT @@ -72,6 +82,9 @@ static int *bool_pending_values = NULL; static struct dentry *class_dir = NULL; static unsigned long last_class_ino; +/* global data for policy capabilities */ +static struct dentry *policycap_dir = NULL; + extern void selnl_notify_setenforce(int val); /* Check whether a task is allowed to use a security operation. */ @@ -111,10 +124,11 @@ enum sel_inos { static unsigned long sel_last_ino = SEL_INO_NEXT - 1; -#define SEL_INITCON_INO_OFFSET 0x01000000 -#define SEL_BOOL_INO_OFFSET 0x02000000 -#define SEL_CLASS_INO_OFFSET 0x04000000 -#define SEL_INO_MASK 0x00ffffff +#define SEL_INITCON_INO_OFFSET 0x01000000 +#define SEL_BOOL_INO_OFFSET 0x02000000 +#define SEL_CLASS_INO_OFFSET 0x04000000 +#define SEL_POLICYCAP_INO_OFFSET 0x08000000 +#define SEL_INO_MASK 0x00ffffff #define TMPBUFLEN 12 static ssize_t sel_read_enforce(struct file *filp, char __user *buf, @@ -263,6 +277,7 @@ static const struct file_operations sel_policyvers_ops = { /* declaration for sel_write_load */ static int sel_make_bools(void); static int sel_make_classes(void); +static int sel_make_policycap(void); /* declaration for sel_make_class_dirs */ static int sel_make_dir(struct inode *dir, struct dentry *dentry, @@ -323,6 +338,12 @@ static ssize_t sel_write_load(struct file * file, const char __user * buf, } ret = sel_make_classes(); + if (ret) { + length = ret; + goto out1; + } + + ret = sel_make_policycap(); if (ret) length = ret; else @@ -1399,6 +1420,24 @@ static const struct file_operations sel_perm_ops = { .read = sel_read_perm, }; +static ssize_t sel_read_policycap(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + int value; + char tmpbuf[TMPBUFLEN]; + ssize_t length; + unsigned long i_ino = file->f_path.dentry->d_inode->i_ino; + + value = security_policycap_supported(i_ino & SEL_INO_MASK); + length = scnprintf(tmpbuf, TMPBUFLEN, "%d", value); + + return simple_read_from_buffer(buf, count, ppos, tmpbuf, length); +} + +static const struct file_operations sel_policycap_ops = { + .read = sel_read_policycap, +}; + static int sel_make_perm_files(char *objclass, int classvalue, struct dentry *dir) { @@ -1545,6 +1584,36 @@ static int sel_make_classes(void) return rc; } +static int sel_make_policycap(void) +{ + unsigned int iter; + struct dentry *dentry = NULL; + struct inode *inode = NULL; + + sel_remove_entries(policycap_dir); + + for (iter = 0; iter <= POLICYDB_CAPABILITY_MAX; iter++) { + if (iter < ARRAY_SIZE(policycap_names)) + dentry = d_alloc_name(policycap_dir, + policycap_names[iter]); + else + dentry = d_alloc_name(policycap_dir, "unknown"); + + if (dentry == NULL) + return -ENOMEM; + + inode = sel_make_inode(policycap_dir->d_sb, S_IFREG | S_IRUGO); + if (inode == NULL) + return -ENOMEM; + + inode->i_fop = &sel_policycap_ops; + inode->i_ino = iter | SEL_POLICYCAP_INO_OFFSET; + d_add(dentry, inode); + } + + return 0; +} + static int sel_make_dir(struct inode *dir, struct dentry *dentry, unsigned long *ino) { @@ -1673,6 +1742,18 @@ static int sel_fill_super(struct super_block * sb, void * data, int silent) class_dir = dentry; + dentry = d_alloc_name(sb->s_root, "policy_capabilities"); + if (!dentry) { + ret = -ENOMEM; + goto err; + } + + ret = sel_make_dir(root_inode, dentry, &sel_last_ino); + if (ret) + goto err; + + policycap_dir = dentry; + out: return ret; err: diff --git a/security/selinux/ss/mls.c b/security/selinux/ss/mls.c index 3bbcb5369af9..feaf0a5b828f 100644 --- a/security/selinux/ss/mls.c +++ b/security/selinux/ss/mls.c @@ -562,7 +562,7 @@ void mls_export_netlbl_lvl(struct context *context, if (!selinux_mls_enabled) return; - secattr->mls_lvl = context->range.level[0].sens - 1; + secattr->attr.mls.lvl = context->range.level[0].sens - 1; secattr->flags |= NETLBL_SECATTR_MLS_LVL; } @@ -582,7 +582,7 @@ void mls_import_netlbl_lvl(struct context *context, if (!selinux_mls_enabled) return; - context->range.level[0].sens = secattr->mls_lvl + 1; + context->range.level[0].sens = secattr->attr.mls.lvl + 1; context->range.level[1].sens = context->range.level[0].sens; } @@ -605,8 +605,8 @@ int mls_export_netlbl_cat(struct context *context, return 0; rc = ebitmap_netlbl_export(&context->range.level[0].cat, - &secattr->mls_cat); - if (rc == 0 && secattr->mls_cat != NULL) + &secattr->attr.mls.cat); + if (rc == 0 && secattr->attr.mls.cat != NULL) secattr->flags |= NETLBL_SECATTR_MLS_CAT; return rc; @@ -633,7 +633,7 @@ int mls_import_netlbl_cat(struct context *context, return 0; rc = ebitmap_netlbl_import(&context->range.level[0].cat, - secattr->mls_cat); + secattr->attr.mls.cat); if (rc != 0) goto import_netlbl_cat_failure; diff --git a/security/selinux/ss/policydb.c b/security/selinux/ss/policydb.c index b582aae3c62c..bd7d6a00342d 100644 --- a/security/selinux/ss/policydb.c +++ b/security/selinux/ss/policydb.c @@ -13,6 +13,11 @@ * * Added conditional policy language extensions * + * Updated: Hewlett-Packard + * + * Added support for the policy capability bitmap + * + * Copyright (C) 2007 Hewlett-Packard Development Company, L.P. * Copyright (C) 2004-2005 Trusted Computer Solutions, Inc. * Copyright (C) 2003 - 2004 Tresys Technology, LLC * This program is free software; you can redistribute it and/or modify @@ -102,6 +107,11 @@ static struct policydb_compat_info policydb_compat[] = { .sym_num = SYM_NUM, .ocon_num = OCON_NUM, }, + { + .version = POLICYDB_VERSION_POLCAP, + .sym_num = SYM_NUM, + .ocon_num = OCON_NUM, + } }; static struct policydb_compat_info *policydb_lookup_compat(int version) @@ -183,6 +193,8 @@ static int policydb_init(struct policydb *p) if (rc) goto out_free_symtab; + ebitmap_init(&p->policycaps); + out: return rc; @@ -673,8 +685,8 @@ void policydb_destroy(struct policydb *p) ebitmap_destroy(&p->type_attr_map[i]); } kfree(p->type_attr_map); - kfree(p->undefined_perms); + ebitmap_destroy(&p->policycaps); return; } @@ -1554,6 +1566,10 @@ int policydb_read(struct policydb *p, void *fp) p->reject_unknown = !!(le32_to_cpu(buf[1]) & REJECT_UNKNOWN); p->allow_unknown = !!(le32_to_cpu(buf[1]) & ALLOW_UNKNOWN); + if (p->policyvers >= POLICYDB_VERSION_POLCAP && + ebitmap_read(&p->policycaps, fp) != 0) + goto bad; + info = policydb_lookup_compat(p->policyvers); if (!info) { printk(KERN_ERR "security: unable to find policy compat info " diff --git a/security/selinux/ss/policydb.h b/security/selinux/ss/policydb.h index ed6fc687c66f..c4ce996e202c 100644 --- a/security/selinux/ss/policydb.h +++ b/security/selinux/ss/policydb.h @@ -241,6 +241,8 @@ struct policydb { /* type -> attribute reverse mapping */ struct ebitmap *type_attr_map; + struct ebitmap policycaps; + unsigned int policyvers; unsigned int reject_unknown : 1; diff --git a/security/selinux/ss/services.c b/security/selinux/ss/services.c index 4bf715d4cf29..f96dec1f9258 100644 --- a/security/selinux/ss/services.c +++ b/security/selinux/ss/services.c @@ -16,12 +16,13 @@ * Updated: Hewlett-Packard * * Added support for NetLabel + * Added support for the policy capability bitmap * * Updated: Chad Sellers * * Added validation of kernel classes and permissions * - * Copyright (C) 2006 Hewlett-Packard Development Company, L.P. + * Copyright (C) 2006, 2007 Hewlett-Packard Development Company, L.P. * Copyright (C) 2004-2006 Trusted Computer Solutions, Inc. * Copyright (C) 2003 - 2004, 2006 Tresys Technology, LLC * Copyright (C) 2003 Red Hat, Inc., James Morris @@ -59,6 +60,8 @@ extern void selnl_notify_policyload(u32 seqno); unsigned int policydb_loaded_version; +int selinux_policycap_netpeer; + /* * This is declared in avc.c */ @@ -1299,6 +1302,12 @@ static int convert_context(u32 key, goto out; } +static void security_load_policycaps(void) +{ + selinux_policycap_netpeer = ebitmap_get_bit(&policydb.policycaps, + POLICYDB_CAPABILITY_NETPEER); +} + extern void selinux_complete_init(void); static int security_preserve_bools(struct policydb *p); @@ -1346,6 +1355,7 @@ int security_load_policy(void *data, size_t len) avtab_cache_destroy(); return -EINVAL; } + security_load_policycaps(); policydb_loaded_version = policydb.policyvers; ss_initialized = 1; seqno = ++latest_granting; @@ -1404,6 +1414,7 @@ int security_load_policy(void *data, size_t len) POLICY_WRLOCK; memcpy(&policydb, &newpolicydb, sizeof policydb); sidtab_set(&sidtab, &newsidtab); + security_load_policycaps(); seqno = ++latest_granting; policydb_loaded_version = policydb.policyvers; POLICY_WRUNLOCK; @@ -1478,11 +1489,8 @@ int security_port_sid(u16 domain, * security_netif_sid - Obtain the SID for a network interface. * @name: interface name * @if_sid: interface SID - * @msg_sid: default SID for received packets */ -int security_netif_sid(char *name, - u32 *if_sid, - u32 *msg_sid) +int security_netif_sid(char *name, u32 *if_sid) { int rc = 0; struct ocontext *c; @@ -1510,11 +1518,8 @@ int security_netif_sid(char *name, goto out; } *if_sid = c->sid[0]; - *msg_sid = c->sid[1]; - } else { + } else *if_sid = SECINITSID_NETIF; - *msg_sid = SECINITSID_NETMSG; - } out: POLICY_RDUNLOCK; @@ -2049,6 +2054,91 @@ int security_sid_mls_copy(u32 sid, u32 mls_sid, u32 *new_sid) return rc; } +/** + * security_net_peersid_resolve - Compare and resolve two network peer SIDs + * @nlbl_sid: NetLabel SID + * @nlbl_type: NetLabel labeling protocol type + * @xfrm_sid: XFRM SID + * + * Description: + * Compare the @nlbl_sid and @xfrm_sid values and if the two SIDs can be + * resolved into a single SID it is returned via @peer_sid and the function + * returns zero. Otherwise @peer_sid is set to SECSID_NULL and the function + * returns a negative value. A table summarizing the behavior is below: + * + * | function return | @sid + * ------------------------------+-----------------+----------------- + * no peer labels | 0 | SECSID_NULL + * single peer label | 0 | + * multiple, consistent labels | 0 | + * multiple, inconsistent labels | - | SECSID_NULL + * + */ +int security_net_peersid_resolve(u32 nlbl_sid, u32 nlbl_type, + u32 xfrm_sid, + u32 *peer_sid) +{ + int rc; + struct context *nlbl_ctx; + struct context *xfrm_ctx; + + /* handle the common (which also happens to be the set of easy) cases + * right away, these two if statements catch everything involving a + * single or absent peer SID/label */ + if (xfrm_sid == SECSID_NULL) { + *peer_sid = nlbl_sid; + return 0; + } + /* NOTE: an nlbl_type == NETLBL_NLTYPE_UNLABELED is a "fallback" label + * and is treated as if nlbl_sid == SECSID_NULL when a XFRM SID/label + * is present */ + if (nlbl_sid == SECSID_NULL || nlbl_type == NETLBL_NLTYPE_UNLABELED) { + *peer_sid = xfrm_sid; + return 0; + } + + /* we don't need to check ss_initialized here since the only way both + * nlbl_sid and xfrm_sid are not equal to SECSID_NULL would be if the + * security server was initialized and ss_initialized was true */ + if (!selinux_mls_enabled) { + *peer_sid = SECSID_NULL; + return 0; + } + + POLICY_RDLOCK; + + nlbl_ctx = sidtab_search(&sidtab, nlbl_sid); + if (!nlbl_ctx) { + printk(KERN_ERR + "security_sid_mls_cmp: unrecognized SID %d\n", + nlbl_sid); + rc = -EINVAL; + goto out_slowpath; + } + xfrm_ctx = sidtab_search(&sidtab, xfrm_sid); + if (!xfrm_ctx) { + printk(KERN_ERR + "security_sid_mls_cmp: unrecognized SID %d\n", + xfrm_sid); + rc = -EINVAL; + goto out_slowpath; + } + rc = (mls_context_cmp(nlbl_ctx, xfrm_ctx) ? 0 : -EACCES); + +out_slowpath: + POLICY_RDUNLOCK; + if (rc == 0) + /* at present NetLabel SIDs/labels really only carry MLS + * information so if the MLS portion of the NetLabel SID + * matches the MLS portion of the labeled XFRM SID/label + * then pass along the XFRM SID as it is the most + * expressive */ + *peer_sid = xfrm_sid; + else + *peer_sid = SECSID_NULL; + return rc; +} + static int get_classes_callback(void *k, void *d, void *args) { struct class_datum *datum = d; @@ -2154,6 +2244,60 @@ int security_get_allow_unknown(void) return policydb.allow_unknown; } +/** + * security_get_policycaps - Query the loaded policy for its capabilities + * @len: the number of capability bits + * @values: the capability bit array + * + * Description: + * Get an array of the policy capabilities in @values where each entry in + * @values is either true (1) or false (0) depending the policy's support of + * that feature. The policy capabilities are defined by the + * POLICYDB_CAPABILITY_* enums. The size of the array is stored in @len and it + * is up to the caller to free the array in @values. Returns zero on success, + * negative values on failure. + * + */ +int security_get_policycaps(int *len, int **values) +{ + int rc = -ENOMEM; + unsigned int iter; + + POLICY_RDLOCK; + + *values = kcalloc(POLICYDB_CAPABILITY_MAX, sizeof(int), GFP_ATOMIC); + if (*values == NULL) + goto out; + for (iter = 0; iter < POLICYDB_CAPABILITY_MAX; iter++) + (*values)[iter] = ebitmap_get_bit(&policydb.policycaps, iter); + *len = POLICYDB_CAPABILITY_MAX; + +out: + POLICY_RDUNLOCK; + return rc; +} + +/** + * security_policycap_supported - Check for a specific policy capability + * @req_cap: capability + * + * Description: + * This function queries the currently loaded policy to see if it supports the + * capability specified by @req_cap. Returns true (1) if the capability is + * supported, false (0) if it isn't supported. + * + */ +int security_policycap_supported(unsigned int req_cap) +{ + int rc; + + POLICY_RDLOCK; + rc = ebitmap_get_bit(&policydb.policycaps, req_cap); + POLICY_RDUNLOCK; + + return rc; +} + struct selinux_audit_rule { u32 au_seqno; struct context au_ctxt; @@ -2403,50 +2547,10 @@ void selinux_audit_set_callback(int (*callback)(void)) } #ifdef CONFIG_NETLABEL -/* - * NetLabel cache structure - */ -#define NETLBL_CACHE(x) ((struct selinux_netlbl_cache *)(x)) -#define NETLBL_CACHE_T_NONE 0 -#define NETLBL_CACHE_T_SID 1 -#define NETLBL_CACHE_T_MLS 2 -struct selinux_netlbl_cache { - u32 type; - union { - u32 sid; - struct mls_range mls_label; - } data; -}; - -/** - * security_netlbl_cache_free - Free the NetLabel cached data - * @data: the data to free - * - * Description: - * This function is intended to be used as the free() callback inside the - * netlbl_lsm_cache structure. - * - */ -static void security_netlbl_cache_free(const void *data) -{ - struct selinux_netlbl_cache *cache; - - if (data == NULL) - return; - - cache = NETLBL_CACHE(data); - switch (cache->type) { - case NETLBL_CACHE_T_MLS: - ebitmap_destroy(&cache->data.mls_label.level[0].cat); - break; - } - kfree(data); -} - /** * security_netlbl_cache_add - Add an entry to the NetLabel cache * @secattr: the NetLabel packet security attributes - * @ctx: the SELinux context + * @sid: the SELinux SID * * Description: * Attempt to cache the context in @ctx, which was derived from the packet in @@ -2455,60 +2559,46 @@ static void security_netlbl_cache_free(const void *data) * */ static void security_netlbl_cache_add(struct netlbl_lsm_secattr *secattr, - struct context *ctx) + u32 sid) { - struct selinux_netlbl_cache *cache = NULL; + u32 *sid_cache; + sid_cache = kmalloc(sizeof(*sid_cache), GFP_ATOMIC); + if (sid_cache == NULL) + return; secattr->cache = netlbl_secattr_cache_alloc(GFP_ATOMIC); - if (secattr->cache == NULL) - return; - - cache = kzalloc(sizeof(*cache), GFP_ATOMIC); - if (cache == NULL) - return; - - cache->type = NETLBL_CACHE_T_MLS; - if (ebitmap_cpy(&cache->data.mls_label.level[0].cat, - &ctx->range.level[0].cat) != 0) { - kfree(cache); + if (secattr->cache == NULL) { + kfree(sid_cache); return; } - cache->data.mls_label.level[1].cat.highbit = - cache->data.mls_label.level[0].cat.highbit; - cache->data.mls_label.level[1].cat.node = - cache->data.mls_label.level[0].cat.node; - cache->data.mls_label.level[0].sens = ctx->range.level[0].sens; - cache->data.mls_label.level[1].sens = ctx->range.level[0].sens; - secattr->cache->free = security_netlbl_cache_free; - secattr->cache->data = (void *)cache; + *sid_cache = sid; + secattr->cache->free = kfree; + secattr->cache->data = sid_cache; secattr->flags |= NETLBL_SECATTR_CACHE; } /** * security_netlbl_secattr_to_sid - Convert a NetLabel secattr to a SELinux SID * @secattr: the NetLabel packet security attributes - * @base_sid: the SELinux SID to use as a context for MLS only attributes * @sid: the SELinux SID * * Description: * Convert the given NetLabel security attributes in @secattr into a * SELinux SID. If the @secattr field does not contain a full SELinux - * SID/context then use the context in @base_sid as the foundation. If - * possibile the 'cache' field of @secattr is set and the CACHE flag is set; - * this is to allow the @secattr to be used by NetLabel to cache the secattr to - * SID conversion for future lookups. Returns zero on success, negative - * values on failure. + * SID/context then use SECINITSID_NETMSG as the foundation. If possibile the + * 'cache' field of @secattr is set and the CACHE flag is set; this is to + * allow the @secattr to be used by NetLabel to cache the secattr to SID + * conversion for future lookups. Returns zero on success, negative values on + * failure. * */ int security_netlbl_secattr_to_sid(struct netlbl_lsm_secattr *secattr, - u32 base_sid, u32 *sid) { int rc = -EIDRM; struct context *ctx; struct context ctx_new; - struct selinux_netlbl_cache *cache; if (!ss_initialized) { *sid = SECSID_NULL; @@ -2518,40 +2608,13 @@ int security_netlbl_secattr_to_sid(struct netlbl_lsm_secattr *secattr, POLICY_RDLOCK; if (secattr->flags & NETLBL_SECATTR_CACHE) { - cache = NETLBL_CACHE(secattr->cache->data); - switch (cache->type) { - case NETLBL_CACHE_T_SID: - *sid = cache->data.sid; - rc = 0; - break; - case NETLBL_CACHE_T_MLS: - ctx = sidtab_search(&sidtab, base_sid); - if (ctx == NULL) - goto netlbl_secattr_to_sid_return; - - ctx_new.user = ctx->user; - ctx_new.role = ctx->role; - ctx_new.type = ctx->type; - ctx_new.range.level[0].sens = - cache->data.mls_label.level[0].sens; - ctx_new.range.level[0].cat.highbit = - cache->data.mls_label.level[0].cat.highbit; - ctx_new.range.level[0].cat.node = - cache->data.mls_label.level[0].cat.node; - ctx_new.range.level[1].sens = - cache->data.mls_label.level[1].sens; - ctx_new.range.level[1].cat.highbit = - cache->data.mls_label.level[1].cat.highbit; - ctx_new.range.level[1].cat.node = - cache->data.mls_label.level[1].cat.node; - - rc = sidtab_context_to_sid(&sidtab, &ctx_new, sid); - break; - default: - goto netlbl_secattr_to_sid_return; - } + *sid = *(u32 *)secattr->cache->data; + rc = 0; + } else if (secattr->flags & NETLBL_SECATTR_SECID) { + *sid = secattr->attr.secid; + rc = 0; } else if (secattr->flags & NETLBL_SECATTR_MLS_LVL) { - ctx = sidtab_search(&sidtab, base_sid); + ctx = sidtab_search(&sidtab, SECINITSID_NETMSG); if (ctx == NULL) goto netlbl_secattr_to_sid_return; @@ -2561,7 +2624,7 @@ int security_netlbl_secattr_to_sid(struct netlbl_lsm_secattr *secattr, mls_import_netlbl_lvl(&ctx_new, secattr); if (secattr->flags & NETLBL_SECATTR_MLS_CAT) { if (ebitmap_netlbl_import(&ctx_new.range.level[0].cat, - secattr->mls_cat) != 0) + secattr->attr.mls.cat) != 0) goto netlbl_secattr_to_sid_return; ctx_new.range.level[1].cat.highbit = ctx_new.range.level[0].cat.highbit; @@ -2578,7 +2641,7 @@ int security_netlbl_secattr_to_sid(struct netlbl_lsm_secattr *secattr, if (rc != 0) goto netlbl_secattr_to_sid_return_cleanup; - security_netlbl_cache_add(secattr, &ctx_new); + security_netlbl_cache_add(secattr, *sid); ebitmap_destroy(&ctx_new.range.level[0].cat); } else { diff --git a/security/selinux/xfrm.c b/security/selinux/xfrm.c index e07603969033..7e158205d081 100644 --- a/security/selinux/xfrm.c +++ b/security/selinux/xfrm.c @@ -46,11 +46,14 @@ #include #include #include +#include #include "avc.h" #include "objsec.h" #include "xfrm.h" +/* Labeled XFRM instance counter */ +atomic_t selinux_xfrm_refcount = ATOMIC_INIT(0); /* * Returns true if an LSM/SELinux context @@ -293,6 +296,9 @@ int selinux_xfrm_policy_alloc(struct xfrm_policy *xp, BUG_ON(!uctx); err = selinux_xfrm_sec_ctx_alloc(&xp->security, uctx, 0); + if (err == 0) + atomic_inc(&selinux_xfrm_refcount); + return err; } @@ -340,10 +346,13 @@ int selinux_xfrm_policy_delete(struct xfrm_policy *xp) struct xfrm_sec_ctx *ctx = xp->security; int rc = 0; - if (ctx) + if (ctx) { rc = avc_has_perm(tsec->sid, ctx->ctx_sid, SECCLASS_ASSOCIATION, ASSOCIATION__SETCONTEXT, NULL); + if (rc == 0) + atomic_dec(&selinux_xfrm_refcount); + } return rc; } @@ -360,6 +369,8 @@ int selinux_xfrm_state_alloc(struct xfrm_state *x, struct xfrm_user_sec_ctx *uct BUG_ON(!x); err = selinux_xfrm_sec_ctx_alloc(&x->security, uctx, secid); + if (err == 0) + atomic_inc(&selinux_xfrm_refcount); return err; } @@ -382,10 +393,13 @@ int selinux_xfrm_state_delete(struct xfrm_state *x) struct xfrm_sec_ctx *ctx = x->security; int rc = 0; - if (ctx) + if (ctx) { rc = avc_has_perm(tsec->sid, ctx->ctx_sid, SECCLASS_ASSOCIATION, ASSOCIATION__SETCONTEXT, NULL); + if (rc == 0) + atomic_dec(&selinux_xfrm_refcount); + } return rc; }