162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * CALIPSO - Common Architecture Label IPv6 Security Option
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * This is an implementation of the CALIPSO protocol as specified in
662306a36Sopenharmony_ci * RFC 5570.
762306a36Sopenharmony_ci *
862306a36Sopenharmony_ci * Authors: Paul Moore <paul.moore@hp.com>
962306a36Sopenharmony_ci *          Huw Davies <huw@codeweavers.com>
1062306a36Sopenharmony_ci */
1162306a36Sopenharmony_ci
1262306a36Sopenharmony_ci/* (c) Copyright Hewlett-Packard Development Company, L.P., 2006, 2008
1362306a36Sopenharmony_ci * (c) Copyright Huw Davies <huw@codeweavers.com>, 2015
1462306a36Sopenharmony_ci */
1562306a36Sopenharmony_ci
1662306a36Sopenharmony_ci#include <linux/init.h>
1762306a36Sopenharmony_ci#include <linux/types.h>
1862306a36Sopenharmony_ci#include <linux/rcupdate.h>
1962306a36Sopenharmony_ci#include <linux/list.h>
2062306a36Sopenharmony_ci#include <linux/spinlock.h>
2162306a36Sopenharmony_ci#include <linux/string.h>
2262306a36Sopenharmony_ci#include <linux/jhash.h>
2362306a36Sopenharmony_ci#include <linux/audit.h>
2462306a36Sopenharmony_ci#include <linux/slab.h>
2562306a36Sopenharmony_ci#include <net/ip.h>
2662306a36Sopenharmony_ci#include <net/icmp.h>
2762306a36Sopenharmony_ci#include <net/tcp.h>
2862306a36Sopenharmony_ci#include <net/netlabel.h>
2962306a36Sopenharmony_ci#include <net/calipso.h>
3062306a36Sopenharmony_ci#include <linux/atomic.h>
3162306a36Sopenharmony_ci#include <linux/bug.h>
3262306a36Sopenharmony_ci#include <asm/unaligned.h>
3362306a36Sopenharmony_ci#include <linux/crc-ccitt.h>
3462306a36Sopenharmony_ci
3562306a36Sopenharmony_ci/* Maximium size of the calipso option including
3662306a36Sopenharmony_ci * the two-byte TLV header.
3762306a36Sopenharmony_ci */
3862306a36Sopenharmony_ci#define CALIPSO_OPT_LEN_MAX (2 + 252)
3962306a36Sopenharmony_ci
4062306a36Sopenharmony_ci/* Size of the minimum calipso option including
4162306a36Sopenharmony_ci * the two-byte TLV header.
4262306a36Sopenharmony_ci */
4362306a36Sopenharmony_ci#define CALIPSO_HDR_LEN (2 + 8)
4462306a36Sopenharmony_ci
4562306a36Sopenharmony_ci/* Maximium size of the calipso option including
4662306a36Sopenharmony_ci * the two-byte TLV header and upto 3 bytes of
4762306a36Sopenharmony_ci * leading pad and 7 bytes of trailing pad.
4862306a36Sopenharmony_ci */
4962306a36Sopenharmony_ci#define CALIPSO_OPT_LEN_MAX_WITH_PAD (3 + CALIPSO_OPT_LEN_MAX + 7)
5062306a36Sopenharmony_ci
5162306a36Sopenharmony_ci /* Maximium size of u32 aligned buffer required to hold calipso
5262306a36Sopenharmony_ci  * option.  Max of 3 initial pad bytes starting from buffer + 3.
5362306a36Sopenharmony_ci  * i.e. the worst case is when the previous tlv finishes on 4n + 3.
5462306a36Sopenharmony_ci  */
5562306a36Sopenharmony_ci#define CALIPSO_MAX_BUFFER (6 + CALIPSO_OPT_LEN_MAX)
5662306a36Sopenharmony_ci
5762306a36Sopenharmony_ci/* List of available DOI definitions */
5862306a36Sopenharmony_cistatic DEFINE_SPINLOCK(calipso_doi_list_lock);
5962306a36Sopenharmony_cistatic LIST_HEAD(calipso_doi_list);
6062306a36Sopenharmony_ci
6162306a36Sopenharmony_ci/* Label mapping cache */
6262306a36Sopenharmony_ciint calipso_cache_enabled = 1;
6362306a36Sopenharmony_ciint calipso_cache_bucketsize = 10;
6462306a36Sopenharmony_ci#define CALIPSO_CACHE_BUCKETBITS     7
6562306a36Sopenharmony_ci#define CALIPSO_CACHE_BUCKETS        BIT(CALIPSO_CACHE_BUCKETBITS)
6662306a36Sopenharmony_ci#define CALIPSO_CACHE_REORDERLIMIT   10
6762306a36Sopenharmony_cistruct calipso_map_cache_bkt {
6862306a36Sopenharmony_ci	spinlock_t lock;
6962306a36Sopenharmony_ci	u32 size;
7062306a36Sopenharmony_ci	struct list_head list;
7162306a36Sopenharmony_ci};
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_cistruct calipso_map_cache_entry {
7462306a36Sopenharmony_ci	u32 hash;
7562306a36Sopenharmony_ci	unsigned char *key;
7662306a36Sopenharmony_ci	size_t key_len;
7762306a36Sopenharmony_ci
7862306a36Sopenharmony_ci	struct netlbl_lsm_cache *lsm_data;
7962306a36Sopenharmony_ci
8062306a36Sopenharmony_ci	u32 activity;
8162306a36Sopenharmony_ci	struct list_head list;
8262306a36Sopenharmony_ci};
8362306a36Sopenharmony_ci
8462306a36Sopenharmony_cistatic struct calipso_map_cache_bkt *calipso_cache;
8562306a36Sopenharmony_ci
8662306a36Sopenharmony_cistatic void calipso_cache_invalidate(void);
8762306a36Sopenharmony_cistatic void calipso_doi_putdef(struct calipso_doi *doi_def);
8862306a36Sopenharmony_ci
8962306a36Sopenharmony_ci/* Label Mapping Cache Functions
9062306a36Sopenharmony_ci */
9162306a36Sopenharmony_ci
9262306a36Sopenharmony_ci/**
9362306a36Sopenharmony_ci * calipso_cache_entry_free - Frees a cache entry
9462306a36Sopenharmony_ci * @entry: the entry to free
9562306a36Sopenharmony_ci *
9662306a36Sopenharmony_ci * Description:
9762306a36Sopenharmony_ci * This function frees the memory associated with a cache entry including the
9862306a36Sopenharmony_ci * LSM cache data if there are no longer any users, i.e. reference count == 0.
9962306a36Sopenharmony_ci *
10062306a36Sopenharmony_ci */
10162306a36Sopenharmony_cistatic void calipso_cache_entry_free(struct calipso_map_cache_entry *entry)
10262306a36Sopenharmony_ci{
10362306a36Sopenharmony_ci	if (entry->lsm_data)
10462306a36Sopenharmony_ci		netlbl_secattr_cache_free(entry->lsm_data);
10562306a36Sopenharmony_ci	kfree(entry->key);
10662306a36Sopenharmony_ci	kfree(entry);
10762306a36Sopenharmony_ci}
10862306a36Sopenharmony_ci
10962306a36Sopenharmony_ci/**
11062306a36Sopenharmony_ci * calipso_map_cache_hash - Hashing function for the CALIPSO cache
11162306a36Sopenharmony_ci * @key: the hash key
11262306a36Sopenharmony_ci * @key_len: the length of the key in bytes
11362306a36Sopenharmony_ci *
11462306a36Sopenharmony_ci * Description:
11562306a36Sopenharmony_ci * The CALIPSO tag hashing function.  Returns a 32-bit hash value.
11662306a36Sopenharmony_ci *
11762306a36Sopenharmony_ci */
11862306a36Sopenharmony_cistatic u32 calipso_map_cache_hash(const unsigned char *key, u32 key_len)
11962306a36Sopenharmony_ci{
12062306a36Sopenharmony_ci	return jhash(key, key_len, 0);
12162306a36Sopenharmony_ci}
12262306a36Sopenharmony_ci
12362306a36Sopenharmony_ci/**
12462306a36Sopenharmony_ci * calipso_cache_init - Initialize the CALIPSO cache
12562306a36Sopenharmony_ci *
12662306a36Sopenharmony_ci * Description:
12762306a36Sopenharmony_ci * Initializes the CALIPSO label mapping cache, this function should be called
12862306a36Sopenharmony_ci * before any of the other functions defined in this file.  Returns zero on
12962306a36Sopenharmony_ci * success, negative values on error.
13062306a36Sopenharmony_ci *
13162306a36Sopenharmony_ci */
13262306a36Sopenharmony_cistatic int __init calipso_cache_init(void)
13362306a36Sopenharmony_ci{
13462306a36Sopenharmony_ci	u32 iter;
13562306a36Sopenharmony_ci
13662306a36Sopenharmony_ci	calipso_cache = kcalloc(CALIPSO_CACHE_BUCKETS,
13762306a36Sopenharmony_ci				sizeof(struct calipso_map_cache_bkt),
13862306a36Sopenharmony_ci				GFP_KERNEL);
13962306a36Sopenharmony_ci	if (!calipso_cache)
14062306a36Sopenharmony_ci		return -ENOMEM;
14162306a36Sopenharmony_ci
14262306a36Sopenharmony_ci	for (iter = 0; iter < CALIPSO_CACHE_BUCKETS; iter++) {
14362306a36Sopenharmony_ci		spin_lock_init(&calipso_cache[iter].lock);
14462306a36Sopenharmony_ci		calipso_cache[iter].size = 0;
14562306a36Sopenharmony_ci		INIT_LIST_HEAD(&calipso_cache[iter].list);
14662306a36Sopenharmony_ci	}
14762306a36Sopenharmony_ci
14862306a36Sopenharmony_ci	return 0;
14962306a36Sopenharmony_ci}
15062306a36Sopenharmony_ci
15162306a36Sopenharmony_ci/**
15262306a36Sopenharmony_ci * calipso_cache_invalidate - Invalidates the current CALIPSO cache
15362306a36Sopenharmony_ci *
15462306a36Sopenharmony_ci * Description:
15562306a36Sopenharmony_ci * Invalidates and frees any entries in the CALIPSO cache.  Returns zero on
15662306a36Sopenharmony_ci * success and negative values on failure.
15762306a36Sopenharmony_ci *
15862306a36Sopenharmony_ci */
15962306a36Sopenharmony_cistatic void calipso_cache_invalidate(void)
16062306a36Sopenharmony_ci{
16162306a36Sopenharmony_ci	struct calipso_map_cache_entry *entry, *tmp_entry;
16262306a36Sopenharmony_ci	u32 iter;
16362306a36Sopenharmony_ci
16462306a36Sopenharmony_ci	for (iter = 0; iter < CALIPSO_CACHE_BUCKETS; iter++) {
16562306a36Sopenharmony_ci		spin_lock_bh(&calipso_cache[iter].lock);
16662306a36Sopenharmony_ci		list_for_each_entry_safe(entry,
16762306a36Sopenharmony_ci					 tmp_entry,
16862306a36Sopenharmony_ci					 &calipso_cache[iter].list, list) {
16962306a36Sopenharmony_ci			list_del(&entry->list);
17062306a36Sopenharmony_ci			calipso_cache_entry_free(entry);
17162306a36Sopenharmony_ci		}
17262306a36Sopenharmony_ci		calipso_cache[iter].size = 0;
17362306a36Sopenharmony_ci		spin_unlock_bh(&calipso_cache[iter].lock);
17462306a36Sopenharmony_ci	}
17562306a36Sopenharmony_ci}
17662306a36Sopenharmony_ci
17762306a36Sopenharmony_ci/**
17862306a36Sopenharmony_ci * calipso_cache_check - Check the CALIPSO cache for a label mapping
17962306a36Sopenharmony_ci * @key: the buffer to check
18062306a36Sopenharmony_ci * @key_len: buffer length in bytes
18162306a36Sopenharmony_ci * @secattr: the security attribute struct to use
18262306a36Sopenharmony_ci *
18362306a36Sopenharmony_ci * Description:
18462306a36Sopenharmony_ci * This function checks the cache to see if a label mapping already exists for
18562306a36Sopenharmony_ci * the given key.  If there is a match then the cache is adjusted and the
18662306a36Sopenharmony_ci * @secattr struct is populated with the correct LSM security attributes.  The
18762306a36Sopenharmony_ci * cache is adjusted in the following manner if the entry is not already the
18862306a36Sopenharmony_ci * first in the cache bucket:
18962306a36Sopenharmony_ci *
19062306a36Sopenharmony_ci *  1. The cache entry's activity counter is incremented
19162306a36Sopenharmony_ci *  2. The previous (higher ranking) entry's activity counter is decremented
19262306a36Sopenharmony_ci *  3. If the difference between the two activity counters is geater than
19362306a36Sopenharmony_ci *     CALIPSO_CACHE_REORDERLIMIT the two entries are swapped
19462306a36Sopenharmony_ci *
19562306a36Sopenharmony_ci * Returns zero on success, -ENOENT for a cache miss, and other negative values
19662306a36Sopenharmony_ci * on error.
19762306a36Sopenharmony_ci *
19862306a36Sopenharmony_ci */
19962306a36Sopenharmony_cistatic int calipso_cache_check(const unsigned char *key,
20062306a36Sopenharmony_ci			       u32 key_len,
20162306a36Sopenharmony_ci			       struct netlbl_lsm_secattr *secattr)
20262306a36Sopenharmony_ci{
20362306a36Sopenharmony_ci	u32 bkt;
20462306a36Sopenharmony_ci	struct calipso_map_cache_entry *entry;
20562306a36Sopenharmony_ci	struct calipso_map_cache_entry *prev_entry = NULL;
20662306a36Sopenharmony_ci	u32 hash;
20762306a36Sopenharmony_ci
20862306a36Sopenharmony_ci	if (!calipso_cache_enabled)
20962306a36Sopenharmony_ci		return -ENOENT;
21062306a36Sopenharmony_ci
21162306a36Sopenharmony_ci	hash = calipso_map_cache_hash(key, key_len);
21262306a36Sopenharmony_ci	bkt = hash & (CALIPSO_CACHE_BUCKETS - 1);
21362306a36Sopenharmony_ci	spin_lock_bh(&calipso_cache[bkt].lock);
21462306a36Sopenharmony_ci	list_for_each_entry(entry, &calipso_cache[bkt].list, list) {
21562306a36Sopenharmony_ci		if (entry->hash == hash &&
21662306a36Sopenharmony_ci		    entry->key_len == key_len &&
21762306a36Sopenharmony_ci		    memcmp(entry->key, key, key_len) == 0) {
21862306a36Sopenharmony_ci			entry->activity += 1;
21962306a36Sopenharmony_ci			refcount_inc(&entry->lsm_data->refcount);
22062306a36Sopenharmony_ci			secattr->cache = entry->lsm_data;
22162306a36Sopenharmony_ci			secattr->flags |= NETLBL_SECATTR_CACHE;
22262306a36Sopenharmony_ci			secattr->type = NETLBL_NLTYPE_CALIPSO;
22362306a36Sopenharmony_ci			if (!prev_entry) {
22462306a36Sopenharmony_ci				spin_unlock_bh(&calipso_cache[bkt].lock);
22562306a36Sopenharmony_ci				return 0;
22662306a36Sopenharmony_ci			}
22762306a36Sopenharmony_ci
22862306a36Sopenharmony_ci			if (prev_entry->activity > 0)
22962306a36Sopenharmony_ci				prev_entry->activity -= 1;
23062306a36Sopenharmony_ci			if (entry->activity > prev_entry->activity &&
23162306a36Sopenharmony_ci			    entry->activity - prev_entry->activity >
23262306a36Sopenharmony_ci			    CALIPSO_CACHE_REORDERLIMIT) {
23362306a36Sopenharmony_ci				__list_del(entry->list.prev, entry->list.next);
23462306a36Sopenharmony_ci				__list_add(&entry->list,
23562306a36Sopenharmony_ci					   prev_entry->list.prev,
23662306a36Sopenharmony_ci					   &prev_entry->list);
23762306a36Sopenharmony_ci			}
23862306a36Sopenharmony_ci
23962306a36Sopenharmony_ci			spin_unlock_bh(&calipso_cache[bkt].lock);
24062306a36Sopenharmony_ci			return 0;
24162306a36Sopenharmony_ci		}
24262306a36Sopenharmony_ci		prev_entry = entry;
24362306a36Sopenharmony_ci	}
24462306a36Sopenharmony_ci	spin_unlock_bh(&calipso_cache[bkt].lock);
24562306a36Sopenharmony_ci
24662306a36Sopenharmony_ci	return -ENOENT;
24762306a36Sopenharmony_ci}
24862306a36Sopenharmony_ci
24962306a36Sopenharmony_ci/**
25062306a36Sopenharmony_ci * calipso_cache_add - Add an entry to the CALIPSO cache
25162306a36Sopenharmony_ci * @calipso_ptr: the CALIPSO option
25262306a36Sopenharmony_ci * @secattr: the packet's security attributes
25362306a36Sopenharmony_ci *
25462306a36Sopenharmony_ci * Description:
25562306a36Sopenharmony_ci * Add a new entry into the CALIPSO label mapping cache.  Add the new entry to
25662306a36Sopenharmony_ci * head of the cache bucket's list, if the cache bucket is out of room remove
25762306a36Sopenharmony_ci * the last entry in the list first.  It is important to note that there is
25862306a36Sopenharmony_ci * currently no checking for duplicate keys.  Returns zero on success,
25962306a36Sopenharmony_ci * negative values on failure.  The key stored starts at calipso_ptr + 2,
26062306a36Sopenharmony_ci * i.e. the type and length bytes are not stored, this corresponds to
26162306a36Sopenharmony_ci * calipso_ptr[1] bytes of data.
26262306a36Sopenharmony_ci *
26362306a36Sopenharmony_ci */
26462306a36Sopenharmony_cistatic int calipso_cache_add(const unsigned char *calipso_ptr,
26562306a36Sopenharmony_ci			     const struct netlbl_lsm_secattr *secattr)
26662306a36Sopenharmony_ci{
26762306a36Sopenharmony_ci	int ret_val = -EPERM;
26862306a36Sopenharmony_ci	u32 bkt;
26962306a36Sopenharmony_ci	struct calipso_map_cache_entry *entry = NULL;
27062306a36Sopenharmony_ci	struct calipso_map_cache_entry *old_entry = NULL;
27162306a36Sopenharmony_ci	u32 calipso_ptr_len;
27262306a36Sopenharmony_ci
27362306a36Sopenharmony_ci	if (!calipso_cache_enabled || calipso_cache_bucketsize <= 0)
27462306a36Sopenharmony_ci		return 0;
27562306a36Sopenharmony_ci
27662306a36Sopenharmony_ci	calipso_ptr_len = calipso_ptr[1];
27762306a36Sopenharmony_ci
27862306a36Sopenharmony_ci	entry = kzalloc(sizeof(*entry), GFP_ATOMIC);
27962306a36Sopenharmony_ci	if (!entry)
28062306a36Sopenharmony_ci		return -ENOMEM;
28162306a36Sopenharmony_ci	entry->key = kmemdup(calipso_ptr + 2, calipso_ptr_len, GFP_ATOMIC);
28262306a36Sopenharmony_ci	if (!entry->key) {
28362306a36Sopenharmony_ci		ret_val = -ENOMEM;
28462306a36Sopenharmony_ci		goto cache_add_failure;
28562306a36Sopenharmony_ci	}
28662306a36Sopenharmony_ci	entry->key_len = calipso_ptr_len;
28762306a36Sopenharmony_ci	entry->hash = calipso_map_cache_hash(calipso_ptr, calipso_ptr_len);
28862306a36Sopenharmony_ci	refcount_inc(&secattr->cache->refcount);
28962306a36Sopenharmony_ci	entry->lsm_data = secattr->cache;
29062306a36Sopenharmony_ci
29162306a36Sopenharmony_ci	bkt = entry->hash & (CALIPSO_CACHE_BUCKETS - 1);
29262306a36Sopenharmony_ci	spin_lock_bh(&calipso_cache[bkt].lock);
29362306a36Sopenharmony_ci	if (calipso_cache[bkt].size < calipso_cache_bucketsize) {
29462306a36Sopenharmony_ci		list_add(&entry->list, &calipso_cache[bkt].list);
29562306a36Sopenharmony_ci		calipso_cache[bkt].size += 1;
29662306a36Sopenharmony_ci	} else {
29762306a36Sopenharmony_ci		old_entry = list_entry(calipso_cache[bkt].list.prev,
29862306a36Sopenharmony_ci				       struct calipso_map_cache_entry, list);
29962306a36Sopenharmony_ci		list_del(&old_entry->list);
30062306a36Sopenharmony_ci		list_add(&entry->list, &calipso_cache[bkt].list);
30162306a36Sopenharmony_ci		calipso_cache_entry_free(old_entry);
30262306a36Sopenharmony_ci	}
30362306a36Sopenharmony_ci	spin_unlock_bh(&calipso_cache[bkt].lock);
30462306a36Sopenharmony_ci
30562306a36Sopenharmony_ci	return 0;
30662306a36Sopenharmony_ci
30762306a36Sopenharmony_cicache_add_failure:
30862306a36Sopenharmony_ci	if (entry)
30962306a36Sopenharmony_ci		calipso_cache_entry_free(entry);
31062306a36Sopenharmony_ci	return ret_val;
31162306a36Sopenharmony_ci}
31262306a36Sopenharmony_ci
31362306a36Sopenharmony_ci/* DOI List Functions
31462306a36Sopenharmony_ci */
31562306a36Sopenharmony_ci
31662306a36Sopenharmony_ci/**
31762306a36Sopenharmony_ci * calipso_doi_search - Searches for a DOI definition
31862306a36Sopenharmony_ci * @doi: the DOI to search for
31962306a36Sopenharmony_ci *
32062306a36Sopenharmony_ci * Description:
32162306a36Sopenharmony_ci * Search the DOI definition list for a DOI definition with a DOI value that
32262306a36Sopenharmony_ci * matches @doi.  The caller is responsible for calling rcu_read_[un]lock().
32362306a36Sopenharmony_ci * Returns a pointer to the DOI definition on success and NULL on failure.
32462306a36Sopenharmony_ci */
32562306a36Sopenharmony_cistatic struct calipso_doi *calipso_doi_search(u32 doi)
32662306a36Sopenharmony_ci{
32762306a36Sopenharmony_ci	struct calipso_doi *iter;
32862306a36Sopenharmony_ci
32962306a36Sopenharmony_ci	list_for_each_entry_rcu(iter, &calipso_doi_list, list)
33062306a36Sopenharmony_ci		if (iter->doi == doi && refcount_read(&iter->refcount))
33162306a36Sopenharmony_ci			return iter;
33262306a36Sopenharmony_ci	return NULL;
33362306a36Sopenharmony_ci}
33462306a36Sopenharmony_ci
33562306a36Sopenharmony_ci/**
33662306a36Sopenharmony_ci * calipso_doi_add - Add a new DOI to the CALIPSO protocol engine
33762306a36Sopenharmony_ci * @doi_def: the DOI structure
33862306a36Sopenharmony_ci * @audit_info: NetLabel audit information
33962306a36Sopenharmony_ci *
34062306a36Sopenharmony_ci * Description:
34162306a36Sopenharmony_ci * The caller defines a new DOI for use by the CALIPSO engine and calls this
34262306a36Sopenharmony_ci * function to add it to the list of acceptable domains.  The caller must
34362306a36Sopenharmony_ci * ensure that the mapping table specified in @doi_def->map meets all of the
34462306a36Sopenharmony_ci * requirements of the mapping type (see calipso.h for details).  Returns
34562306a36Sopenharmony_ci * zero on success and non-zero on failure.
34662306a36Sopenharmony_ci *
34762306a36Sopenharmony_ci */
34862306a36Sopenharmony_cistatic int calipso_doi_add(struct calipso_doi *doi_def,
34962306a36Sopenharmony_ci			   struct netlbl_audit *audit_info)
35062306a36Sopenharmony_ci{
35162306a36Sopenharmony_ci	int ret_val = -EINVAL;
35262306a36Sopenharmony_ci	u32 doi;
35362306a36Sopenharmony_ci	u32 doi_type;
35462306a36Sopenharmony_ci	struct audit_buffer *audit_buf;
35562306a36Sopenharmony_ci
35662306a36Sopenharmony_ci	doi = doi_def->doi;
35762306a36Sopenharmony_ci	doi_type = doi_def->type;
35862306a36Sopenharmony_ci
35962306a36Sopenharmony_ci	if (doi_def->doi == CALIPSO_DOI_UNKNOWN)
36062306a36Sopenharmony_ci		goto doi_add_return;
36162306a36Sopenharmony_ci
36262306a36Sopenharmony_ci	refcount_set(&doi_def->refcount, 1);
36362306a36Sopenharmony_ci
36462306a36Sopenharmony_ci	spin_lock(&calipso_doi_list_lock);
36562306a36Sopenharmony_ci	if (calipso_doi_search(doi_def->doi)) {
36662306a36Sopenharmony_ci		spin_unlock(&calipso_doi_list_lock);
36762306a36Sopenharmony_ci		ret_val = -EEXIST;
36862306a36Sopenharmony_ci		goto doi_add_return;
36962306a36Sopenharmony_ci	}
37062306a36Sopenharmony_ci	list_add_tail_rcu(&doi_def->list, &calipso_doi_list);
37162306a36Sopenharmony_ci	spin_unlock(&calipso_doi_list_lock);
37262306a36Sopenharmony_ci	ret_val = 0;
37362306a36Sopenharmony_ci
37462306a36Sopenharmony_cidoi_add_return:
37562306a36Sopenharmony_ci	audit_buf = netlbl_audit_start(AUDIT_MAC_CALIPSO_ADD, audit_info);
37662306a36Sopenharmony_ci	if (audit_buf) {
37762306a36Sopenharmony_ci		const char *type_str;
37862306a36Sopenharmony_ci
37962306a36Sopenharmony_ci		switch (doi_type) {
38062306a36Sopenharmony_ci		case CALIPSO_MAP_PASS:
38162306a36Sopenharmony_ci			type_str = "pass";
38262306a36Sopenharmony_ci			break;
38362306a36Sopenharmony_ci		default:
38462306a36Sopenharmony_ci			type_str = "(unknown)";
38562306a36Sopenharmony_ci		}
38662306a36Sopenharmony_ci		audit_log_format(audit_buf,
38762306a36Sopenharmony_ci				 " calipso_doi=%u calipso_type=%s res=%u",
38862306a36Sopenharmony_ci				 doi, type_str, ret_val == 0 ? 1 : 0);
38962306a36Sopenharmony_ci		audit_log_end(audit_buf);
39062306a36Sopenharmony_ci	}
39162306a36Sopenharmony_ci
39262306a36Sopenharmony_ci	return ret_val;
39362306a36Sopenharmony_ci}
39462306a36Sopenharmony_ci
39562306a36Sopenharmony_ci/**
39662306a36Sopenharmony_ci * calipso_doi_free - Frees a DOI definition
39762306a36Sopenharmony_ci * @doi_def: the DOI definition
39862306a36Sopenharmony_ci *
39962306a36Sopenharmony_ci * Description:
40062306a36Sopenharmony_ci * This function frees all of the memory associated with a DOI definition.
40162306a36Sopenharmony_ci *
40262306a36Sopenharmony_ci */
40362306a36Sopenharmony_cistatic void calipso_doi_free(struct calipso_doi *doi_def)
40462306a36Sopenharmony_ci{
40562306a36Sopenharmony_ci	kfree(doi_def);
40662306a36Sopenharmony_ci}
40762306a36Sopenharmony_ci
40862306a36Sopenharmony_ci/**
40962306a36Sopenharmony_ci * calipso_doi_free_rcu - Frees a DOI definition via the RCU pointer
41062306a36Sopenharmony_ci * @entry: the entry's RCU field
41162306a36Sopenharmony_ci *
41262306a36Sopenharmony_ci * Description:
41362306a36Sopenharmony_ci * This function is designed to be used as a callback to the call_rcu()
41462306a36Sopenharmony_ci * function so that the memory allocated to the DOI definition can be released
41562306a36Sopenharmony_ci * safely.
41662306a36Sopenharmony_ci *
41762306a36Sopenharmony_ci */
41862306a36Sopenharmony_cistatic void calipso_doi_free_rcu(struct rcu_head *entry)
41962306a36Sopenharmony_ci{
42062306a36Sopenharmony_ci	struct calipso_doi *doi_def;
42162306a36Sopenharmony_ci
42262306a36Sopenharmony_ci	doi_def = container_of(entry, struct calipso_doi, rcu);
42362306a36Sopenharmony_ci	calipso_doi_free(doi_def);
42462306a36Sopenharmony_ci}
42562306a36Sopenharmony_ci
42662306a36Sopenharmony_ci/**
42762306a36Sopenharmony_ci * calipso_doi_remove - Remove an existing DOI from the CALIPSO protocol engine
42862306a36Sopenharmony_ci * @doi: the DOI value
42962306a36Sopenharmony_ci * @audit_info: NetLabel audit information
43062306a36Sopenharmony_ci *
43162306a36Sopenharmony_ci * Description:
43262306a36Sopenharmony_ci * Removes a DOI definition from the CALIPSO engine.  The NetLabel routines will
43362306a36Sopenharmony_ci * be called to release their own LSM domain mappings as well as our own
43462306a36Sopenharmony_ci * domain list.  Returns zero on success and negative values on failure.
43562306a36Sopenharmony_ci *
43662306a36Sopenharmony_ci */
43762306a36Sopenharmony_cistatic int calipso_doi_remove(u32 doi, struct netlbl_audit *audit_info)
43862306a36Sopenharmony_ci{
43962306a36Sopenharmony_ci	int ret_val;
44062306a36Sopenharmony_ci	struct calipso_doi *doi_def;
44162306a36Sopenharmony_ci	struct audit_buffer *audit_buf;
44262306a36Sopenharmony_ci
44362306a36Sopenharmony_ci	spin_lock(&calipso_doi_list_lock);
44462306a36Sopenharmony_ci	doi_def = calipso_doi_search(doi);
44562306a36Sopenharmony_ci	if (!doi_def) {
44662306a36Sopenharmony_ci		spin_unlock(&calipso_doi_list_lock);
44762306a36Sopenharmony_ci		ret_val = -ENOENT;
44862306a36Sopenharmony_ci		goto doi_remove_return;
44962306a36Sopenharmony_ci	}
45062306a36Sopenharmony_ci	list_del_rcu(&doi_def->list);
45162306a36Sopenharmony_ci	spin_unlock(&calipso_doi_list_lock);
45262306a36Sopenharmony_ci
45362306a36Sopenharmony_ci	calipso_doi_putdef(doi_def);
45462306a36Sopenharmony_ci	ret_val = 0;
45562306a36Sopenharmony_ci
45662306a36Sopenharmony_cidoi_remove_return:
45762306a36Sopenharmony_ci	audit_buf = netlbl_audit_start(AUDIT_MAC_CALIPSO_DEL, audit_info);
45862306a36Sopenharmony_ci	if (audit_buf) {
45962306a36Sopenharmony_ci		audit_log_format(audit_buf,
46062306a36Sopenharmony_ci				 " calipso_doi=%u res=%u",
46162306a36Sopenharmony_ci				 doi, ret_val == 0 ? 1 : 0);
46262306a36Sopenharmony_ci		audit_log_end(audit_buf);
46362306a36Sopenharmony_ci	}
46462306a36Sopenharmony_ci
46562306a36Sopenharmony_ci	return ret_val;
46662306a36Sopenharmony_ci}
46762306a36Sopenharmony_ci
46862306a36Sopenharmony_ci/**
46962306a36Sopenharmony_ci * calipso_doi_getdef - Returns a reference to a valid DOI definition
47062306a36Sopenharmony_ci * @doi: the DOI value
47162306a36Sopenharmony_ci *
47262306a36Sopenharmony_ci * Description:
47362306a36Sopenharmony_ci * Searches for a valid DOI definition and if one is found it is returned to
47462306a36Sopenharmony_ci * the caller.  Otherwise NULL is returned.  The caller must ensure that
47562306a36Sopenharmony_ci * calipso_doi_putdef() is called when the caller is done.
47662306a36Sopenharmony_ci *
47762306a36Sopenharmony_ci */
47862306a36Sopenharmony_cistatic struct calipso_doi *calipso_doi_getdef(u32 doi)
47962306a36Sopenharmony_ci{
48062306a36Sopenharmony_ci	struct calipso_doi *doi_def;
48162306a36Sopenharmony_ci
48262306a36Sopenharmony_ci	rcu_read_lock();
48362306a36Sopenharmony_ci	doi_def = calipso_doi_search(doi);
48462306a36Sopenharmony_ci	if (!doi_def)
48562306a36Sopenharmony_ci		goto doi_getdef_return;
48662306a36Sopenharmony_ci	if (!refcount_inc_not_zero(&doi_def->refcount))
48762306a36Sopenharmony_ci		doi_def = NULL;
48862306a36Sopenharmony_ci
48962306a36Sopenharmony_cidoi_getdef_return:
49062306a36Sopenharmony_ci	rcu_read_unlock();
49162306a36Sopenharmony_ci	return doi_def;
49262306a36Sopenharmony_ci}
49362306a36Sopenharmony_ci
49462306a36Sopenharmony_ci/**
49562306a36Sopenharmony_ci * calipso_doi_putdef - Releases a reference for the given DOI definition
49662306a36Sopenharmony_ci * @doi_def: the DOI definition
49762306a36Sopenharmony_ci *
49862306a36Sopenharmony_ci * Description:
49962306a36Sopenharmony_ci * Releases a DOI definition reference obtained from calipso_doi_getdef().
50062306a36Sopenharmony_ci *
50162306a36Sopenharmony_ci */
50262306a36Sopenharmony_cistatic void calipso_doi_putdef(struct calipso_doi *doi_def)
50362306a36Sopenharmony_ci{
50462306a36Sopenharmony_ci	if (!doi_def)
50562306a36Sopenharmony_ci		return;
50662306a36Sopenharmony_ci
50762306a36Sopenharmony_ci	if (!refcount_dec_and_test(&doi_def->refcount))
50862306a36Sopenharmony_ci		return;
50962306a36Sopenharmony_ci
51062306a36Sopenharmony_ci	calipso_cache_invalidate();
51162306a36Sopenharmony_ci	call_rcu(&doi_def->rcu, calipso_doi_free_rcu);
51262306a36Sopenharmony_ci}
51362306a36Sopenharmony_ci
51462306a36Sopenharmony_ci/**
51562306a36Sopenharmony_ci * calipso_doi_walk - Iterate through the DOI definitions
51662306a36Sopenharmony_ci * @skip_cnt: skip past this number of DOI definitions, updated
51762306a36Sopenharmony_ci * @callback: callback for each DOI definition
51862306a36Sopenharmony_ci * @cb_arg: argument for the callback function
51962306a36Sopenharmony_ci *
52062306a36Sopenharmony_ci * Description:
52162306a36Sopenharmony_ci * Iterate over the DOI definition list, skipping the first @skip_cnt entries.
52262306a36Sopenharmony_ci * For each entry call @callback, if @callback returns a negative value stop
52362306a36Sopenharmony_ci * 'walking' through the list and return.  Updates the value in @skip_cnt upon
52462306a36Sopenharmony_ci * return.  Returns zero on success, negative values on failure.
52562306a36Sopenharmony_ci *
52662306a36Sopenharmony_ci */
52762306a36Sopenharmony_cistatic int calipso_doi_walk(u32 *skip_cnt,
52862306a36Sopenharmony_ci			    int (*callback)(struct calipso_doi *doi_def,
52962306a36Sopenharmony_ci					    void *arg),
53062306a36Sopenharmony_ci			    void *cb_arg)
53162306a36Sopenharmony_ci{
53262306a36Sopenharmony_ci	int ret_val = -ENOENT;
53362306a36Sopenharmony_ci	u32 doi_cnt = 0;
53462306a36Sopenharmony_ci	struct calipso_doi *iter_doi;
53562306a36Sopenharmony_ci
53662306a36Sopenharmony_ci	rcu_read_lock();
53762306a36Sopenharmony_ci	list_for_each_entry_rcu(iter_doi, &calipso_doi_list, list)
53862306a36Sopenharmony_ci		if (refcount_read(&iter_doi->refcount) > 0) {
53962306a36Sopenharmony_ci			if (doi_cnt++ < *skip_cnt)
54062306a36Sopenharmony_ci				continue;
54162306a36Sopenharmony_ci			ret_val = callback(iter_doi, cb_arg);
54262306a36Sopenharmony_ci			if (ret_val < 0) {
54362306a36Sopenharmony_ci				doi_cnt--;
54462306a36Sopenharmony_ci				goto doi_walk_return;
54562306a36Sopenharmony_ci			}
54662306a36Sopenharmony_ci		}
54762306a36Sopenharmony_ci
54862306a36Sopenharmony_cidoi_walk_return:
54962306a36Sopenharmony_ci	rcu_read_unlock();
55062306a36Sopenharmony_ci	*skip_cnt = doi_cnt;
55162306a36Sopenharmony_ci	return ret_val;
55262306a36Sopenharmony_ci}
55362306a36Sopenharmony_ci
55462306a36Sopenharmony_ci/**
55562306a36Sopenharmony_ci * calipso_validate - Validate a CALIPSO option
55662306a36Sopenharmony_ci * @skb: the packet
55762306a36Sopenharmony_ci * @option: the start of the option
55862306a36Sopenharmony_ci *
55962306a36Sopenharmony_ci * Description:
56062306a36Sopenharmony_ci * This routine is called to validate a CALIPSO option.
56162306a36Sopenharmony_ci * If the option is valid then %true is returned, otherwise
56262306a36Sopenharmony_ci * %false is returned.
56362306a36Sopenharmony_ci *
56462306a36Sopenharmony_ci * The caller should have already checked that the length of the
56562306a36Sopenharmony_ci * option (including the TLV header) is >= 10 and that the catmap
56662306a36Sopenharmony_ci * length is consistent with the option length.
56762306a36Sopenharmony_ci *
56862306a36Sopenharmony_ci * We leave checks on the level and categories to the socket layer.
56962306a36Sopenharmony_ci */
57062306a36Sopenharmony_cibool calipso_validate(const struct sk_buff *skb, const unsigned char *option)
57162306a36Sopenharmony_ci{
57262306a36Sopenharmony_ci	struct calipso_doi *doi_def;
57362306a36Sopenharmony_ci	bool ret_val;
57462306a36Sopenharmony_ci	u16 crc, len = option[1] + 2;
57562306a36Sopenharmony_ci	static const u8 zero[2];
57662306a36Sopenharmony_ci
57762306a36Sopenharmony_ci	/* The original CRC runs over the option including the TLV header
57862306a36Sopenharmony_ci	 * with the CRC-16 field (at offset 8) zeroed out. */
57962306a36Sopenharmony_ci	crc = crc_ccitt(0xffff, option, 8);
58062306a36Sopenharmony_ci	crc = crc_ccitt(crc, zero, sizeof(zero));
58162306a36Sopenharmony_ci	if (len > 10)
58262306a36Sopenharmony_ci		crc = crc_ccitt(crc, option + 10, len - 10);
58362306a36Sopenharmony_ci	crc = ~crc;
58462306a36Sopenharmony_ci	if (option[8] != (crc & 0xff) || option[9] != ((crc >> 8) & 0xff))
58562306a36Sopenharmony_ci		return false;
58662306a36Sopenharmony_ci
58762306a36Sopenharmony_ci	rcu_read_lock();
58862306a36Sopenharmony_ci	doi_def = calipso_doi_search(get_unaligned_be32(option + 2));
58962306a36Sopenharmony_ci	ret_val = !!doi_def;
59062306a36Sopenharmony_ci	rcu_read_unlock();
59162306a36Sopenharmony_ci
59262306a36Sopenharmony_ci	return ret_val;
59362306a36Sopenharmony_ci}
59462306a36Sopenharmony_ci
59562306a36Sopenharmony_ci/**
59662306a36Sopenharmony_ci * calipso_map_cat_hton - Perform a category mapping from host to network
59762306a36Sopenharmony_ci * @doi_def: the DOI definition
59862306a36Sopenharmony_ci * @secattr: the security attributes
59962306a36Sopenharmony_ci * @net_cat: the zero'd out category bitmap in network/CALIPSO format
60062306a36Sopenharmony_ci * @net_cat_len: the length of the CALIPSO bitmap in bytes
60162306a36Sopenharmony_ci *
60262306a36Sopenharmony_ci * Description:
60362306a36Sopenharmony_ci * Perform a label mapping to translate a local MLS category bitmap to the
60462306a36Sopenharmony_ci * correct CALIPSO bitmap using the given DOI definition.  Returns the minimum
60562306a36Sopenharmony_ci * size in bytes of the network bitmap on success, negative values otherwise.
60662306a36Sopenharmony_ci *
60762306a36Sopenharmony_ci */
60862306a36Sopenharmony_cistatic int calipso_map_cat_hton(const struct calipso_doi *doi_def,
60962306a36Sopenharmony_ci				const struct netlbl_lsm_secattr *secattr,
61062306a36Sopenharmony_ci				unsigned char *net_cat,
61162306a36Sopenharmony_ci				u32 net_cat_len)
61262306a36Sopenharmony_ci{
61362306a36Sopenharmony_ci	int spot = -1;
61462306a36Sopenharmony_ci	u32 net_spot_max = 0;
61562306a36Sopenharmony_ci	u32 net_clen_bits = net_cat_len * 8;
61662306a36Sopenharmony_ci
61762306a36Sopenharmony_ci	for (;;) {
61862306a36Sopenharmony_ci		spot = netlbl_catmap_walk(secattr->attr.mls.cat,
61962306a36Sopenharmony_ci					  spot + 1);
62062306a36Sopenharmony_ci		if (spot < 0)
62162306a36Sopenharmony_ci			break;
62262306a36Sopenharmony_ci		if (spot >= net_clen_bits)
62362306a36Sopenharmony_ci			return -ENOSPC;
62462306a36Sopenharmony_ci		netlbl_bitmap_setbit(net_cat, spot, 1);
62562306a36Sopenharmony_ci
62662306a36Sopenharmony_ci		if (spot > net_spot_max)
62762306a36Sopenharmony_ci			net_spot_max = spot;
62862306a36Sopenharmony_ci	}
62962306a36Sopenharmony_ci
63062306a36Sopenharmony_ci	return (net_spot_max / 32 + 1) * 4;
63162306a36Sopenharmony_ci}
63262306a36Sopenharmony_ci
63362306a36Sopenharmony_ci/**
63462306a36Sopenharmony_ci * calipso_map_cat_ntoh - Perform a category mapping from network to host
63562306a36Sopenharmony_ci * @doi_def: the DOI definition
63662306a36Sopenharmony_ci * @net_cat: the category bitmap in network/CALIPSO format
63762306a36Sopenharmony_ci * @net_cat_len: the length of the CALIPSO bitmap in bytes
63862306a36Sopenharmony_ci * @secattr: the security attributes
63962306a36Sopenharmony_ci *
64062306a36Sopenharmony_ci * Description:
64162306a36Sopenharmony_ci * Perform a label mapping to translate a CALIPSO bitmap to the correct local
64262306a36Sopenharmony_ci * MLS category bitmap using the given DOI definition.  Returns zero on
64362306a36Sopenharmony_ci * success, negative values on failure.
64462306a36Sopenharmony_ci *
64562306a36Sopenharmony_ci */
64662306a36Sopenharmony_cistatic int calipso_map_cat_ntoh(const struct calipso_doi *doi_def,
64762306a36Sopenharmony_ci				const unsigned char *net_cat,
64862306a36Sopenharmony_ci				u32 net_cat_len,
64962306a36Sopenharmony_ci				struct netlbl_lsm_secattr *secattr)
65062306a36Sopenharmony_ci{
65162306a36Sopenharmony_ci	int ret_val;
65262306a36Sopenharmony_ci	int spot = -1;
65362306a36Sopenharmony_ci	u32 net_clen_bits = net_cat_len * 8;
65462306a36Sopenharmony_ci
65562306a36Sopenharmony_ci	for (;;) {
65662306a36Sopenharmony_ci		spot = netlbl_bitmap_walk(net_cat,
65762306a36Sopenharmony_ci					  net_clen_bits,
65862306a36Sopenharmony_ci					  spot + 1,
65962306a36Sopenharmony_ci					  1);
66062306a36Sopenharmony_ci		if (spot < 0) {
66162306a36Sopenharmony_ci			if (spot == -2)
66262306a36Sopenharmony_ci				return -EFAULT;
66362306a36Sopenharmony_ci			return 0;
66462306a36Sopenharmony_ci		}
66562306a36Sopenharmony_ci
66662306a36Sopenharmony_ci		ret_val = netlbl_catmap_setbit(&secattr->attr.mls.cat,
66762306a36Sopenharmony_ci					       spot,
66862306a36Sopenharmony_ci					       GFP_ATOMIC);
66962306a36Sopenharmony_ci		if (ret_val != 0)
67062306a36Sopenharmony_ci			return ret_val;
67162306a36Sopenharmony_ci	}
67262306a36Sopenharmony_ci
67362306a36Sopenharmony_ci	return -EINVAL;
67462306a36Sopenharmony_ci}
67562306a36Sopenharmony_ci
67662306a36Sopenharmony_ci/**
67762306a36Sopenharmony_ci * calipso_pad_write - Writes pad bytes in TLV format
67862306a36Sopenharmony_ci * @buf: the buffer
67962306a36Sopenharmony_ci * @offset: offset from start of buffer to write padding
68062306a36Sopenharmony_ci * @count: number of pad bytes to write
68162306a36Sopenharmony_ci *
68262306a36Sopenharmony_ci * Description:
68362306a36Sopenharmony_ci * Write @count bytes of TLV padding into @buffer starting at offset @offset.
68462306a36Sopenharmony_ci * @count should be less than 8 - see RFC 4942.
68562306a36Sopenharmony_ci *
68662306a36Sopenharmony_ci */
68762306a36Sopenharmony_cistatic int calipso_pad_write(unsigned char *buf, unsigned int offset,
68862306a36Sopenharmony_ci			     unsigned int count)
68962306a36Sopenharmony_ci{
69062306a36Sopenharmony_ci	if (WARN_ON_ONCE(count >= 8))
69162306a36Sopenharmony_ci		return -EINVAL;
69262306a36Sopenharmony_ci
69362306a36Sopenharmony_ci	switch (count) {
69462306a36Sopenharmony_ci	case 0:
69562306a36Sopenharmony_ci		break;
69662306a36Sopenharmony_ci	case 1:
69762306a36Sopenharmony_ci		buf[offset] = IPV6_TLV_PAD1;
69862306a36Sopenharmony_ci		break;
69962306a36Sopenharmony_ci	default:
70062306a36Sopenharmony_ci		buf[offset] = IPV6_TLV_PADN;
70162306a36Sopenharmony_ci		buf[offset + 1] = count - 2;
70262306a36Sopenharmony_ci		if (count > 2)
70362306a36Sopenharmony_ci			memset(buf + offset + 2, 0, count - 2);
70462306a36Sopenharmony_ci		break;
70562306a36Sopenharmony_ci	}
70662306a36Sopenharmony_ci	return 0;
70762306a36Sopenharmony_ci}
70862306a36Sopenharmony_ci
70962306a36Sopenharmony_ci/**
71062306a36Sopenharmony_ci * calipso_genopt - Generate a CALIPSO option
71162306a36Sopenharmony_ci * @buf: the option buffer
71262306a36Sopenharmony_ci * @start: offset from which to write
71362306a36Sopenharmony_ci * @buf_len: the size of opt_buf
71462306a36Sopenharmony_ci * @doi_def: the CALIPSO DOI to use
71562306a36Sopenharmony_ci * @secattr: the security attributes
71662306a36Sopenharmony_ci *
71762306a36Sopenharmony_ci * Description:
71862306a36Sopenharmony_ci * Generate a CALIPSO option using the DOI definition and security attributes
71962306a36Sopenharmony_ci * passed to the function. This also generates upto three bytes of leading
72062306a36Sopenharmony_ci * padding that ensures that the option is 4n + 2 aligned.  It returns the
72162306a36Sopenharmony_ci * number of bytes written (including any initial padding).
72262306a36Sopenharmony_ci */
72362306a36Sopenharmony_cistatic int calipso_genopt(unsigned char *buf, u32 start, u32 buf_len,
72462306a36Sopenharmony_ci			  const struct calipso_doi *doi_def,
72562306a36Sopenharmony_ci			  const struct netlbl_lsm_secattr *secattr)
72662306a36Sopenharmony_ci{
72762306a36Sopenharmony_ci	int ret_val;
72862306a36Sopenharmony_ci	u32 len, pad;
72962306a36Sopenharmony_ci	u16 crc;
73062306a36Sopenharmony_ci	static const unsigned char padding[4] = {2, 1, 0, 3};
73162306a36Sopenharmony_ci	unsigned char *calipso;
73262306a36Sopenharmony_ci
73362306a36Sopenharmony_ci	/* CALIPSO has 4n + 2 alignment */
73462306a36Sopenharmony_ci	pad = padding[start & 3];
73562306a36Sopenharmony_ci	if (buf_len <= start + pad + CALIPSO_HDR_LEN)
73662306a36Sopenharmony_ci		return -ENOSPC;
73762306a36Sopenharmony_ci
73862306a36Sopenharmony_ci	if ((secattr->flags & NETLBL_SECATTR_MLS_LVL) == 0)
73962306a36Sopenharmony_ci		return -EPERM;
74062306a36Sopenharmony_ci
74162306a36Sopenharmony_ci	len = CALIPSO_HDR_LEN;
74262306a36Sopenharmony_ci
74362306a36Sopenharmony_ci	if (secattr->flags & NETLBL_SECATTR_MLS_CAT) {
74462306a36Sopenharmony_ci		ret_val = calipso_map_cat_hton(doi_def,
74562306a36Sopenharmony_ci					       secattr,
74662306a36Sopenharmony_ci					       buf + start + pad + len,
74762306a36Sopenharmony_ci					       buf_len - start - pad - len);
74862306a36Sopenharmony_ci		if (ret_val < 0)
74962306a36Sopenharmony_ci			return ret_val;
75062306a36Sopenharmony_ci		len += ret_val;
75162306a36Sopenharmony_ci	}
75262306a36Sopenharmony_ci
75362306a36Sopenharmony_ci	calipso_pad_write(buf, start, pad);
75462306a36Sopenharmony_ci	calipso = buf + start + pad;
75562306a36Sopenharmony_ci
75662306a36Sopenharmony_ci	calipso[0] = IPV6_TLV_CALIPSO;
75762306a36Sopenharmony_ci	calipso[1] = len - 2;
75862306a36Sopenharmony_ci	*(__be32 *)(calipso + 2) = htonl(doi_def->doi);
75962306a36Sopenharmony_ci	calipso[6] = (len - CALIPSO_HDR_LEN) / 4;
76062306a36Sopenharmony_ci	calipso[7] = secattr->attr.mls.lvl;
76162306a36Sopenharmony_ci	crc = ~crc_ccitt(0xffff, calipso, len);
76262306a36Sopenharmony_ci	calipso[8] = crc & 0xff;
76362306a36Sopenharmony_ci	calipso[9] = (crc >> 8) & 0xff;
76462306a36Sopenharmony_ci	return pad + len;
76562306a36Sopenharmony_ci}
76662306a36Sopenharmony_ci
76762306a36Sopenharmony_ci/* Hop-by-hop hdr helper functions
76862306a36Sopenharmony_ci */
76962306a36Sopenharmony_ci
77062306a36Sopenharmony_ci/**
77162306a36Sopenharmony_ci * calipso_opt_update - Replaces socket's hop options with a new set
77262306a36Sopenharmony_ci * @sk: the socket
77362306a36Sopenharmony_ci * @hop: new hop options
77462306a36Sopenharmony_ci *
77562306a36Sopenharmony_ci * Description:
77662306a36Sopenharmony_ci * Replaces @sk's hop options with @hop.  @hop may be NULL to leave
77762306a36Sopenharmony_ci * the socket with no hop options.
77862306a36Sopenharmony_ci *
77962306a36Sopenharmony_ci */
78062306a36Sopenharmony_cistatic int calipso_opt_update(struct sock *sk, struct ipv6_opt_hdr *hop)
78162306a36Sopenharmony_ci{
78262306a36Sopenharmony_ci	struct ipv6_txoptions *old = txopt_get(inet6_sk(sk)), *txopts;
78362306a36Sopenharmony_ci
78462306a36Sopenharmony_ci	txopts = ipv6_renew_options(sk, old, IPV6_HOPOPTS, hop);
78562306a36Sopenharmony_ci	txopt_put(old);
78662306a36Sopenharmony_ci	if (IS_ERR(txopts))
78762306a36Sopenharmony_ci		return PTR_ERR(txopts);
78862306a36Sopenharmony_ci
78962306a36Sopenharmony_ci	txopts = ipv6_update_options(sk, txopts);
79062306a36Sopenharmony_ci	if (txopts) {
79162306a36Sopenharmony_ci		atomic_sub(txopts->tot_len, &sk->sk_omem_alloc);
79262306a36Sopenharmony_ci		txopt_put(txopts);
79362306a36Sopenharmony_ci	}
79462306a36Sopenharmony_ci
79562306a36Sopenharmony_ci	return 0;
79662306a36Sopenharmony_ci}
79762306a36Sopenharmony_ci
79862306a36Sopenharmony_ci/**
79962306a36Sopenharmony_ci * calipso_tlv_len - Returns the length of the TLV
80062306a36Sopenharmony_ci * @opt: the option header
80162306a36Sopenharmony_ci * @offset: offset of the TLV within the header
80262306a36Sopenharmony_ci *
80362306a36Sopenharmony_ci * Description:
80462306a36Sopenharmony_ci * Returns the length of the TLV option at offset @offset within
80562306a36Sopenharmony_ci * the option header @opt.  Checks that the entire TLV fits inside
80662306a36Sopenharmony_ci * the option header, returns a negative value if this is not the case.
80762306a36Sopenharmony_ci */
80862306a36Sopenharmony_cistatic int calipso_tlv_len(struct ipv6_opt_hdr *opt, unsigned int offset)
80962306a36Sopenharmony_ci{
81062306a36Sopenharmony_ci	unsigned char *tlv = (unsigned char *)opt;
81162306a36Sopenharmony_ci	unsigned int opt_len = ipv6_optlen(opt), tlv_len;
81262306a36Sopenharmony_ci
81362306a36Sopenharmony_ci	if (offset < sizeof(*opt) || offset >= opt_len)
81462306a36Sopenharmony_ci		return -EINVAL;
81562306a36Sopenharmony_ci	if (tlv[offset] == IPV6_TLV_PAD1)
81662306a36Sopenharmony_ci		return 1;
81762306a36Sopenharmony_ci	if (offset + 1 >= opt_len)
81862306a36Sopenharmony_ci		return -EINVAL;
81962306a36Sopenharmony_ci	tlv_len = tlv[offset + 1] + 2;
82062306a36Sopenharmony_ci	if (offset + tlv_len > opt_len)
82162306a36Sopenharmony_ci		return -EINVAL;
82262306a36Sopenharmony_ci	return tlv_len;
82362306a36Sopenharmony_ci}
82462306a36Sopenharmony_ci
82562306a36Sopenharmony_ci/**
82662306a36Sopenharmony_ci * calipso_opt_find - Finds the CALIPSO option in an IPv6 hop options header
82762306a36Sopenharmony_ci * @hop: the hop options header
82862306a36Sopenharmony_ci * @start: on return holds the offset of any leading padding
82962306a36Sopenharmony_ci * @end: on return holds the offset of the first non-pad TLV after CALIPSO
83062306a36Sopenharmony_ci *
83162306a36Sopenharmony_ci * Description:
83262306a36Sopenharmony_ci * Finds the space occupied by a CALIPSO option (including any leading and
83362306a36Sopenharmony_ci * trailing padding).
83462306a36Sopenharmony_ci *
83562306a36Sopenharmony_ci * If a CALIPSO option exists set @start and @end to the
83662306a36Sopenharmony_ci * offsets within @hop of the start of padding before the first
83762306a36Sopenharmony_ci * CALIPSO option and the end of padding after the first CALIPSO
83862306a36Sopenharmony_ci * option.  In this case the function returns 0.
83962306a36Sopenharmony_ci *
84062306a36Sopenharmony_ci * In the absence of a CALIPSO option, @start and @end will be
84162306a36Sopenharmony_ci * set to the start and end of any trailing padding in the header.
84262306a36Sopenharmony_ci * This is useful when appending a new option, as the caller may want
84362306a36Sopenharmony_ci * to overwrite some of this padding.  In this case the function will
84462306a36Sopenharmony_ci * return -ENOENT.
84562306a36Sopenharmony_ci */
84662306a36Sopenharmony_cistatic int calipso_opt_find(struct ipv6_opt_hdr *hop, unsigned int *start,
84762306a36Sopenharmony_ci			    unsigned int *end)
84862306a36Sopenharmony_ci{
84962306a36Sopenharmony_ci	int ret_val = -ENOENT, tlv_len;
85062306a36Sopenharmony_ci	unsigned int opt_len, offset, offset_s = 0, offset_e = 0;
85162306a36Sopenharmony_ci	unsigned char *opt = (unsigned char *)hop;
85262306a36Sopenharmony_ci
85362306a36Sopenharmony_ci	opt_len = ipv6_optlen(hop);
85462306a36Sopenharmony_ci	offset = sizeof(*hop);
85562306a36Sopenharmony_ci
85662306a36Sopenharmony_ci	while (offset < opt_len) {
85762306a36Sopenharmony_ci		tlv_len = calipso_tlv_len(hop, offset);
85862306a36Sopenharmony_ci		if (tlv_len < 0)
85962306a36Sopenharmony_ci			return tlv_len;
86062306a36Sopenharmony_ci
86162306a36Sopenharmony_ci		switch (opt[offset]) {
86262306a36Sopenharmony_ci		case IPV6_TLV_PAD1:
86362306a36Sopenharmony_ci		case IPV6_TLV_PADN:
86462306a36Sopenharmony_ci			if (offset_e)
86562306a36Sopenharmony_ci				offset_e = offset;
86662306a36Sopenharmony_ci			break;
86762306a36Sopenharmony_ci		case IPV6_TLV_CALIPSO:
86862306a36Sopenharmony_ci			ret_val = 0;
86962306a36Sopenharmony_ci			offset_e = offset;
87062306a36Sopenharmony_ci			break;
87162306a36Sopenharmony_ci		default:
87262306a36Sopenharmony_ci			if (offset_e == 0)
87362306a36Sopenharmony_ci				offset_s = offset;
87462306a36Sopenharmony_ci			else
87562306a36Sopenharmony_ci				goto out;
87662306a36Sopenharmony_ci		}
87762306a36Sopenharmony_ci		offset += tlv_len;
87862306a36Sopenharmony_ci	}
87962306a36Sopenharmony_ci
88062306a36Sopenharmony_ciout:
88162306a36Sopenharmony_ci	if (offset_s)
88262306a36Sopenharmony_ci		*start = offset_s + calipso_tlv_len(hop, offset_s);
88362306a36Sopenharmony_ci	else
88462306a36Sopenharmony_ci		*start = sizeof(*hop);
88562306a36Sopenharmony_ci	if (offset_e)
88662306a36Sopenharmony_ci		*end = offset_e + calipso_tlv_len(hop, offset_e);
88762306a36Sopenharmony_ci	else
88862306a36Sopenharmony_ci		*end = opt_len;
88962306a36Sopenharmony_ci
89062306a36Sopenharmony_ci	return ret_val;
89162306a36Sopenharmony_ci}
89262306a36Sopenharmony_ci
89362306a36Sopenharmony_ci/**
89462306a36Sopenharmony_ci * calipso_opt_insert - Inserts a CALIPSO option into an IPv6 hop opt hdr
89562306a36Sopenharmony_ci * @hop: the original hop options header
89662306a36Sopenharmony_ci * @doi_def: the CALIPSO DOI to use
89762306a36Sopenharmony_ci * @secattr: the specific security attributes of the socket
89862306a36Sopenharmony_ci *
89962306a36Sopenharmony_ci * Description:
90062306a36Sopenharmony_ci * Creates a new hop options header based on @hop with a
90162306a36Sopenharmony_ci * CALIPSO option added to it.  If @hop already contains a CALIPSO
90262306a36Sopenharmony_ci * option this is overwritten, otherwise the new option is appended
90362306a36Sopenharmony_ci * after any existing options.  If @hop is NULL then the new header
90462306a36Sopenharmony_ci * will contain just the CALIPSO option and any needed padding.
90562306a36Sopenharmony_ci *
90662306a36Sopenharmony_ci */
90762306a36Sopenharmony_cistatic struct ipv6_opt_hdr *
90862306a36Sopenharmony_cicalipso_opt_insert(struct ipv6_opt_hdr *hop,
90962306a36Sopenharmony_ci		   const struct calipso_doi *doi_def,
91062306a36Sopenharmony_ci		   const struct netlbl_lsm_secattr *secattr)
91162306a36Sopenharmony_ci{
91262306a36Sopenharmony_ci	unsigned int start, end, buf_len, pad, hop_len;
91362306a36Sopenharmony_ci	struct ipv6_opt_hdr *new;
91462306a36Sopenharmony_ci	int ret_val;
91562306a36Sopenharmony_ci
91662306a36Sopenharmony_ci	if (hop) {
91762306a36Sopenharmony_ci		hop_len = ipv6_optlen(hop);
91862306a36Sopenharmony_ci		ret_val = calipso_opt_find(hop, &start, &end);
91962306a36Sopenharmony_ci		if (ret_val && ret_val != -ENOENT)
92062306a36Sopenharmony_ci			return ERR_PTR(ret_val);
92162306a36Sopenharmony_ci	} else {
92262306a36Sopenharmony_ci		hop_len = 0;
92362306a36Sopenharmony_ci		start = sizeof(*hop);
92462306a36Sopenharmony_ci		end = 0;
92562306a36Sopenharmony_ci	}
92662306a36Sopenharmony_ci
92762306a36Sopenharmony_ci	buf_len = hop_len + start - end + CALIPSO_OPT_LEN_MAX_WITH_PAD;
92862306a36Sopenharmony_ci	new = kzalloc(buf_len, GFP_ATOMIC);
92962306a36Sopenharmony_ci	if (!new)
93062306a36Sopenharmony_ci		return ERR_PTR(-ENOMEM);
93162306a36Sopenharmony_ci
93262306a36Sopenharmony_ci	if (start > sizeof(*hop))
93362306a36Sopenharmony_ci		memcpy(new, hop, start);
93462306a36Sopenharmony_ci	ret_val = calipso_genopt((unsigned char *)new, start, buf_len, doi_def,
93562306a36Sopenharmony_ci				 secattr);
93662306a36Sopenharmony_ci	if (ret_val < 0) {
93762306a36Sopenharmony_ci		kfree(new);
93862306a36Sopenharmony_ci		return ERR_PTR(ret_val);
93962306a36Sopenharmony_ci	}
94062306a36Sopenharmony_ci
94162306a36Sopenharmony_ci	buf_len = start + ret_val;
94262306a36Sopenharmony_ci	/* At this point buf_len aligns to 4n, so (buf_len & 4) pads to 8n */
94362306a36Sopenharmony_ci	pad = ((buf_len & 4) + (end & 7)) & 7;
94462306a36Sopenharmony_ci	calipso_pad_write((unsigned char *)new, buf_len, pad);
94562306a36Sopenharmony_ci	buf_len += pad;
94662306a36Sopenharmony_ci
94762306a36Sopenharmony_ci	if (end != hop_len) {
94862306a36Sopenharmony_ci		memcpy((char *)new + buf_len, (char *)hop + end, hop_len - end);
94962306a36Sopenharmony_ci		buf_len += hop_len - end;
95062306a36Sopenharmony_ci	}
95162306a36Sopenharmony_ci	new->nexthdr = 0;
95262306a36Sopenharmony_ci	new->hdrlen = buf_len / 8 - 1;
95362306a36Sopenharmony_ci
95462306a36Sopenharmony_ci	return new;
95562306a36Sopenharmony_ci}
95662306a36Sopenharmony_ci
95762306a36Sopenharmony_ci/**
95862306a36Sopenharmony_ci * calipso_opt_del - Removes the CALIPSO option from an option header
95962306a36Sopenharmony_ci * @hop: the original header
96062306a36Sopenharmony_ci * @new: the new header
96162306a36Sopenharmony_ci *
96262306a36Sopenharmony_ci * Description:
96362306a36Sopenharmony_ci * Creates a new header based on @hop without any CALIPSO option.  If @hop
96462306a36Sopenharmony_ci * doesn't contain a CALIPSO option it returns -ENOENT.  If @hop contains
96562306a36Sopenharmony_ci * no other non-padding options, it returns zero with @new set to NULL.
96662306a36Sopenharmony_ci * Otherwise it returns zero, creates a new header without the CALIPSO
96762306a36Sopenharmony_ci * option (and removing as much padding as possible) and returns with
96862306a36Sopenharmony_ci * @new set to that header.
96962306a36Sopenharmony_ci *
97062306a36Sopenharmony_ci */
97162306a36Sopenharmony_cistatic int calipso_opt_del(struct ipv6_opt_hdr *hop,
97262306a36Sopenharmony_ci			   struct ipv6_opt_hdr **new)
97362306a36Sopenharmony_ci{
97462306a36Sopenharmony_ci	int ret_val;
97562306a36Sopenharmony_ci	unsigned int start, end, delta, pad, hop_len;
97662306a36Sopenharmony_ci
97762306a36Sopenharmony_ci	ret_val = calipso_opt_find(hop, &start, &end);
97862306a36Sopenharmony_ci	if (ret_val)
97962306a36Sopenharmony_ci		return ret_val;
98062306a36Sopenharmony_ci
98162306a36Sopenharmony_ci	hop_len = ipv6_optlen(hop);
98262306a36Sopenharmony_ci	if (start == sizeof(*hop) && end == hop_len) {
98362306a36Sopenharmony_ci		/* There's no other option in the header so return NULL */
98462306a36Sopenharmony_ci		*new = NULL;
98562306a36Sopenharmony_ci		return 0;
98662306a36Sopenharmony_ci	}
98762306a36Sopenharmony_ci
98862306a36Sopenharmony_ci	delta = (end - start) & ~7;
98962306a36Sopenharmony_ci	*new = kzalloc(hop_len - delta, GFP_ATOMIC);
99062306a36Sopenharmony_ci	if (!*new)
99162306a36Sopenharmony_ci		return -ENOMEM;
99262306a36Sopenharmony_ci
99362306a36Sopenharmony_ci	memcpy(*new, hop, start);
99462306a36Sopenharmony_ci	(*new)->hdrlen -= delta / 8;
99562306a36Sopenharmony_ci	pad = (end - start) & 7;
99662306a36Sopenharmony_ci	calipso_pad_write((unsigned char *)*new, start, pad);
99762306a36Sopenharmony_ci	if (end != hop_len)
99862306a36Sopenharmony_ci		memcpy((char *)*new + start + pad, (char *)hop + end,
99962306a36Sopenharmony_ci		       hop_len - end);
100062306a36Sopenharmony_ci
100162306a36Sopenharmony_ci	return 0;
100262306a36Sopenharmony_ci}
100362306a36Sopenharmony_ci
100462306a36Sopenharmony_ci/**
100562306a36Sopenharmony_ci * calipso_opt_getattr - Get the security attributes from a memory block
100662306a36Sopenharmony_ci * @calipso: the CALIPSO option
100762306a36Sopenharmony_ci * @secattr: the security attributes
100862306a36Sopenharmony_ci *
100962306a36Sopenharmony_ci * Description:
101062306a36Sopenharmony_ci * Inspect @calipso and return the security attributes in @secattr.
101162306a36Sopenharmony_ci * Returns zero on success and negative values on failure.
101262306a36Sopenharmony_ci *
101362306a36Sopenharmony_ci */
101462306a36Sopenharmony_cistatic int calipso_opt_getattr(const unsigned char *calipso,
101562306a36Sopenharmony_ci			       struct netlbl_lsm_secattr *secattr)
101662306a36Sopenharmony_ci{
101762306a36Sopenharmony_ci	int ret_val = -ENOMSG;
101862306a36Sopenharmony_ci	u32 doi, len = calipso[1], cat_len = calipso[6] * 4;
101962306a36Sopenharmony_ci	struct calipso_doi *doi_def;
102062306a36Sopenharmony_ci
102162306a36Sopenharmony_ci	if (cat_len + 8 > len)
102262306a36Sopenharmony_ci		return -EINVAL;
102362306a36Sopenharmony_ci
102462306a36Sopenharmony_ci	if (calipso_cache_check(calipso + 2, calipso[1], secattr) == 0)
102562306a36Sopenharmony_ci		return 0;
102662306a36Sopenharmony_ci
102762306a36Sopenharmony_ci	doi = get_unaligned_be32(calipso + 2);
102862306a36Sopenharmony_ci	rcu_read_lock();
102962306a36Sopenharmony_ci	doi_def = calipso_doi_search(doi);
103062306a36Sopenharmony_ci	if (!doi_def)
103162306a36Sopenharmony_ci		goto getattr_return;
103262306a36Sopenharmony_ci
103362306a36Sopenharmony_ci	secattr->attr.mls.lvl = calipso[7];
103462306a36Sopenharmony_ci	secattr->flags |= NETLBL_SECATTR_MLS_LVL;
103562306a36Sopenharmony_ci
103662306a36Sopenharmony_ci	if (cat_len) {
103762306a36Sopenharmony_ci		ret_val = calipso_map_cat_ntoh(doi_def,
103862306a36Sopenharmony_ci					       calipso + 10,
103962306a36Sopenharmony_ci					       cat_len,
104062306a36Sopenharmony_ci					       secattr);
104162306a36Sopenharmony_ci		if (ret_val != 0) {
104262306a36Sopenharmony_ci			netlbl_catmap_free(secattr->attr.mls.cat);
104362306a36Sopenharmony_ci			goto getattr_return;
104462306a36Sopenharmony_ci		}
104562306a36Sopenharmony_ci
104662306a36Sopenharmony_ci		if (secattr->attr.mls.cat)
104762306a36Sopenharmony_ci			secattr->flags |= NETLBL_SECATTR_MLS_CAT;
104862306a36Sopenharmony_ci	}
104962306a36Sopenharmony_ci
105062306a36Sopenharmony_ci	secattr->type = NETLBL_NLTYPE_CALIPSO;
105162306a36Sopenharmony_ci
105262306a36Sopenharmony_cigetattr_return:
105362306a36Sopenharmony_ci	rcu_read_unlock();
105462306a36Sopenharmony_ci	return ret_val;
105562306a36Sopenharmony_ci}
105662306a36Sopenharmony_ci
105762306a36Sopenharmony_ci/* sock functions.
105862306a36Sopenharmony_ci */
105962306a36Sopenharmony_ci
106062306a36Sopenharmony_ci/**
106162306a36Sopenharmony_ci * calipso_sock_getattr - Get the security attributes from a sock
106262306a36Sopenharmony_ci * @sk: the sock
106362306a36Sopenharmony_ci * @secattr: the security attributes
106462306a36Sopenharmony_ci *
106562306a36Sopenharmony_ci * Description:
106662306a36Sopenharmony_ci * Query @sk to see if there is a CALIPSO option attached to the sock and if
106762306a36Sopenharmony_ci * there is return the CALIPSO security attributes in @secattr.  This function
106862306a36Sopenharmony_ci * requires that @sk be locked, or privately held, but it does not do any
106962306a36Sopenharmony_ci * locking itself.  Returns zero on success and negative values on failure.
107062306a36Sopenharmony_ci *
107162306a36Sopenharmony_ci */
107262306a36Sopenharmony_cistatic int calipso_sock_getattr(struct sock *sk,
107362306a36Sopenharmony_ci				struct netlbl_lsm_secattr *secattr)
107462306a36Sopenharmony_ci{
107562306a36Sopenharmony_ci	struct ipv6_opt_hdr *hop;
107662306a36Sopenharmony_ci	int opt_len, len, ret_val = -ENOMSG, offset;
107762306a36Sopenharmony_ci	unsigned char *opt;
107862306a36Sopenharmony_ci	struct ipv6_txoptions *txopts = txopt_get(inet6_sk(sk));
107962306a36Sopenharmony_ci
108062306a36Sopenharmony_ci	if (!txopts || !txopts->hopopt)
108162306a36Sopenharmony_ci		goto done;
108262306a36Sopenharmony_ci
108362306a36Sopenharmony_ci	hop = txopts->hopopt;
108462306a36Sopenharmony_ci	opt = (unsigned char *)hop;
108562306a36Sopenharmony_ci	opt_len = ipv6_optlen(hop);
108662306a36Sopenharmony_ci	offset = sizeof(*hop);
108762306a36Sopenharmony_ci	while (offset < opt_len) {
108862306a36Sopenharmony_ci		len = calipso_tlv_len(hop, offset);
108962306a36Sopenharmony_ci		if (len < 0) {
109062306a36Sopenharmony_ci			ret_val = len;
109162306a36Sopenharmony_ci			goto done;
109262306a36Sopenharmony_ci		}
109362306a36Sopenharmony_ci		switch (opt[offset]) {
109462306a36Sopenharmony_ci		case IPV6_TLV_CALIPSO:
109562306a36Sopenharmony_ci			if (len < CALIPSO_HDR_LEN)
109662306a36Sopenharmony_ci				ret_val = -EINVAL;
109762306a36Sopenharmony_ci			else
109862306a36Sopenharmony_ci				ret_val = calipso_opt_getattr(&opt[offset],
109962306a36Sopenharmony_ci							      secattr);
110062306a36Sopenharmony_ci			goto done;
110162306a36Sopenharmony_ci		default:
110262306a36Sopenharmony_ci			offset += len;
110362306a36Sopenharmony_ci			break;
110462306a36Sopenharmony_ci		}
110562306a36Sopenharmony_ci	}
110662306a36Sopenharmony_cidone:
110762306a36Sopenharmony_ci	txopt_put(txopts);
110862306a36Sopenharmony_ci	return ret_val;
110962306a36Sopenharmony_ci}
111062306a36Sopenharmony_ci
111162306a36Sopenharmony_ci/**
111262306a36Sopenharmony_ci * calipso_sock_setattr - Add a CALIPSO option to a socket
111362306a36Sopenharmony_ci * @sk: the socket
111462306a36Sopenharmony_ci * @doi_def: the CALIPSO DOI to use
111562306a36Sopenharmony_ci * @secattr: the specific security attributes of the socket
111662306a36Sopenharmony_ci *
111762306a36Sopenharmony_ci * Description:
111862306a36Sopenharmony_ci * Set the CALIPSO option on the given socket using the DOI definition and
111962306a36Sopenharmony_ci * security attributes passed to the function.  This function requires
112062306a36Sopenharmony_ci * exclusive access to @sk, which means it either needs to be in the
112162306a36Sopenharmony_ci * process of being created or locked.  Returns zero on success and negative
112262306a36Sopenharmony_ci * values on failure.
112362306a36Sopenharmony_ci *
112462306a36Sopenharmony_ci */
112562306a36Sopenharmony_cistatic int calipso_sock_setattr(struct sock *sk,
112662306a36Sopenharmony_ci				const struct calipso_doi *doi_def,
112762306a36Sopenharmony_ci				const struct netlbl_lsm_secattr *secattr)
112862306a36Sopenharmony_ci{
112962306a36Sopenharmony_ci	int ret_val;
113062306a36Sopenharmony_ci	struct ipv6_opt_hdr *old, *new;
113162306a36Sopenharmony_ci	struct ipv6_txoptions *txopts = txopt_get(inet6_sk(sk));
113262306a36Sopenharmony_ci
113362306a36Sopenharmony_ci	old = NULL;
113462306a36Sopenharmony_ci	if (txopts)
113562306a36Sopenharmony_ci		old = txopts->hopopt;
113662306a36Sopenharmony_ci
113762306a36Sopenharmony_ci	new = calipso_opt_insert(old, doi_def, secattr);
113862306a36Sopenharmony_ci	txopt_put(txopts);
113962306a36Sopenharmony_ci	if (IS_ERR(new))
114062306a36Sopenharmony_ci		return PTR_ERR(new);
114162306a36Sopenharmony_ci
114262306a36Sopenharmony_ci	ret_val = calipso_opt_update(sk, new);
114362306a36Sopenharmony_ci
114462306a36Sopenharmony_ci	kfree(new);
114562306a36Sopenharmony_ci	return ret_val;
114662306a36Sopenharmony_ci}
114762306a36Sopenharmony_ci
114862306a36Sopenharmony_ci/**
114962306a36Sopenharmony_ci * calipso_sock_delattr - Delete the CALIPSO option from a socket
115062306a36Sopenharmony_ci * @sk: the socket
115162306a36Sopenharmony_ci *
115262306a36Sopenharmony_ci * Description:
115362306a36Sopenharmony_ci * Removes the CALIPSO option from a socket, if present.
115462306a36Sopenharmony_ci *
115562306a36Sopenharmony_ci */
115662306a36Sopenharmony_cistatic void calipso_sock_delattr(struct sock *sk)
115762306a36Sopenharmony_ci{
115862306a36Sopenharmony_ci	struct ipv6_opt_hdr *new_hop;
115962306a36Sopenharmony_ci	struct ipv6_txoptions *txopts = txopt_get(inet6_sk(sk));
116062306a36Sopenharmony_ci
116162306a36Sopenharmony_ci	if (!txopts || !txopts->hopopt)
116262306a36Sopenharmony_ci		goto done;
116362306a36Sopenharmony_ci
116462306a36Sopenharmony_ci	if (calipso_opt_del(txopts->hopopt, &new_hop))
116562306a36Sopenharmony_ci		goto done;
116662306a36Sopenharmony_ci
116762306a36Sopenharmony_ci	calipso_opt_update(sk, new_hop);
116862306a36Sopenharmony_ci	kfree(new_hop);
116962306a36Sopenharmony_ci
117062306a36Sopenharmony_cidone:
117162306a36Sopenharmony_ci	txopt_put(txopts);
117262306a36Sopenharmony_ci}
117362306a36Sopenharmony_ci
117462306a36Sopenharmony_ci/* request sock functions.
117562306a36Sopenharmony_ci */
117662306a36Sopenharmony_ci
117762306a36Sopenharmony_ci/**
117862306a36Sopenharmony_ci * calipso_req_setattr - Add a CALIPSO option to a connection request socket
117962306a36Sopenharmony_ci * @req: the connection request socket
118062306a36Sopenharmony_ci * @doi_def: the CALIPSO DOI to use
118162306a36Sopenharmony_ci * @secattr: the specific security attributes of the socket
118262306a36Sopenharmony_ci *
118362306a36Sopenharmony_ci * Description:
118462306a36Sopenharmony_ci * Set the CALIPSO option on the given socket using the DOI definition and
118562306a36Sopenharmony_ci * security attributes passed to the function.  Returns zero on success and
118662306a36Sopenharmony_ci * negative values on failure.
118762306a36Sopenharmony_ci *
118862306a36Sopenharmony_ci */
118962306a36Sopenharmony_cistatic int calipso_req_setattr(struct request_sock *req,
119062306a36Sopenharmony_ci			       const struct calipso_doi *doi_def,
119162306a36Sopenharmony_ci			       const struct netlbl_lsm_secattr *secattr)
119262306a36Sopenharmony_ci{
119362306a36Sopenharmony_ci	struct ipv6_txoptions *txopts;
119462306a36Sopenharmony_ci	struct inet_request_sock *req_inet = inet_rsk(req);
119562306a36Sopenharmony_ci	struct ipv6_opt_hdr *old, *new;
119662306a36Sopenharmony_ci	struct sock *sk = sk_to_full_sk(req_to_sk(req));
119762306a36Sopenharmony_ci
119862306a36Sopenharmony_ci	if (req_inet->ipv6_opt && req_inet->ipv6_opt->hopopt)
119962306a36Sopenharmony_ci		old = req_inet->ipv6_opt->hopopt;
120062306a36Sopenharmony_ci	else
120162306a36Sopenharmony_ci		old = NULL;
120262306a36Sopenharmony_ci
120362306a36Sopenharmony_ci	new = calipso_opt_insert(old, doi_def, secattr);
120462306a36Sopenharmony_ci	if (IS_ERR(new))
120562306a36Sopenharmony_ci		return PTR_ERR(new);
120662306a36Sopenharmony_ci
120762306a36Sopenharmony_ci	txopts = ipv6_renew_options(sk, req_inet->ipv6_opt, IPV6_HOPOPTS, new);
120862306a36Sopenharmony_ci
120962306a36Sopenharmony_ci	kfree(new);
121062306a36Sopenharmony_ci
121162306a36Sopenharmony_ci	if (IS_ERR(txopts))
121262306a36Sopenharmony_ci		return PTR_ERR(txopts);
121362306a36Sopenharmony_ci
121462306a36Sopenharmony_ci	txopts = xchg(&req_inet->ipv6_opt, txopts);
121562306a36Sopenharmony_ci	if (txopts) {
121662306a36Sopenharmony_ci		atomic_sub(txopts->tot_len, &sk->sk_omem_alloc);
121762306a36Sopenharmony_ci		txopt_put(txopts);
121862306a36Sopenharmony_ci	}
121962306a36Sopenharmony_ci
122062306a36Sopenharmony_ci	return 0;
122162306a36Sopenharmony_ci}
122262306a36Sopenharmony_ci
122362306a36Sopenharmony_ci/**
122462306a36Sopenharmony_ci * calipso_req_delattr - Delete the CALIPSO option from a request socket
122562306a36Sopenharmony_ci * @req: the request socket
122662306a36Sopenharmony_ci *
122762306a36Sopenharmony_ci * Description:
122862306a36Sopenharmony_ci * Removes the CALIPSO option from a request socket, if present.
122962306a36Sopenharmony_ci *
123062306a36Sopenharmony_ci */
123162306a36Sopenharmony_cistatic void calipso_req_delattr(struct request_sock *req)
123262306a36Sopenharmony_ci{
123362306a36Sopenharmony_ci	struct inet_request_sock *req_inet = inet_rsk(req);
123462306a36Sopenharmony_ci	struct ipv6_opt_hdr *new;
123562306a36Sopenharmony_ci	struct ipv6_txoptions *txopts;
123662306a36Sopenharmony_ci	struct sock *sk = sk_to_full_sk(req_to_sk(req));
123762306a36Sopenharmony_ci
123862306a36Sopenharmony_ci	if (!req_inet->ipv6_opt || !req_inet->ipv6_opt->hopopt)
123962306a36Sopenharmony_ci		return;
124062306a36Sopenharmony_ci
124162306a36Sopenharmony_ci	if (calipso_opt_del(req_inet->ipv6_opt->hopopt, &new))
124262306a36Sopenharmony_ci		return; /* Nothing to do */
124362306a36Sopenharmony_ci
124462306a36Sopenharmony_ci	txopts = ipv6_renew_options(sk, req_inet->ipv6_opt, IPV6_HOPOPTS, new);
124562306a36Sopenharmony_ci
124662306a36Sopenharmony_ci	if (!IS_ERR(txopts)) {
124762306a36Sopenharmony_ci		txopts = xchg(&req_inet->ipv6_opt, txopts);
124862306a36Sopenharmony_ci		if (txopts) {
124962306a36Sopenharmony_ci			atomic_sub(txopts->tot_len, &sk->sk_omem_alloc);
125062306a36Sopenharmony_ci			txopt_put(txopts);
125162306a36Sopenharmony_ci		}
125262306a36Sopenharmony_ci	}
125362306a36Sopenharmony_ci	kfree(new);
125462306a36Sopenharmony_ci}
125562306a36Sopenharmony_ci
125662306a36Sopenharmony_ci/* skbuff functions.
125762306a36Sopenharmony_ci */
125862306a36Sopenharmony_ci
125962306a36Sopenharmony_ci/**
126062306a36Sopenharmony_ci * calipso_skbuff_optptr - Find the CALIPSO option in the packet
126162306a36Sopenharmony_ci * @skb: the packet
126262306a36Sopenharmony_ci *
126362306a36Sopenharmony_ci * Description:
126462306a36Sopenharmony_ci * Parse the packet's IP header looking for a CALIPSO option.  Returns a pointer
126562306a36Sopenharmony_ci * to the start of the CALIPSO option on success, NULL if one if not found.
126662306a36Sopenharmony_ci *
126762306a36Sopenharmony_ci */
126862306a36Sopenharmony_cistatic unsigned char *calipso_skbuff_optptr(const struct sk_buff *skb)
126962306a36Sopenharmony_ci{
127062306a36Sopenharmony_ci	const struct ipv6hdr *ip6_hdr = ipv6_hdr(skb);
127162306a36Sopenharmony_ci	int offset;
127262306a36Sopenharmony_ci
127362306a36Sopenharmony_ci	if (ip6_hdr->nexthdr != NEXTHDR_HOP)
127462306a36Sopenharmony_ci		return NULL;
127562306a36Sopenharmony_ci
127662306a36Sopenharmony_ci	offset = ipv6_find_tlv(skb, sizeof(*ip6_hdr), IPV6_TLV_CALIPSO);
127762306a36Sopenharmony_ci	if (offset >= 0)
127862306a36Sopenharmony_ci		return (unsigned char *)ip6_hdr + offset;
127962306a36Sopenharmony_ci
128062306a36Sopenharmony_ci	return NULL;
128162306a36Sopenharmony_ci}
128262306a36Sopenharmony_ci
128362306a36Sopenharmony_ci/**
128462306a36Sopenharmony_ci * calipso_skbuff_setattr - Set the CALIPSO option on a packet
128562306a36Sopenharmony_ci * @skb: the packet
128662306a36Sopenharmony_ci * @doi_def: the CALIPSO DOI to use
128762306a36Sopenharmony_ci * @secattr: the security attributes
128862306a36Sopenharmony_ci *
128962306a36Sopenharmony_ci * Description:
129062306a36Sopenharmony_ci * Set the CALIPSO option on the given packet based on the security attributes.
129162306a36Sopenharmony_ci * Returns a pointer to the IP header on success and NULL on failure.
129262306a36Sopenharmony_ci *
129362306a36Sopenharmony_ci */
129462306a36Sopenharmony_cistatic int calipso_skbuff_setattr(struct sk_buff *skb,
129562306a36Sopenharmony_ci				  const struct calipso_doi *doi_def,
129662306a36Sopenharmony_ci				  const struct netlbl_lsm_secattr *secattr)
129762306a36Sopenharmony_ci{
129862306a36Sopenharmony_ci	int ret_val;
129962306a36Sopenharmony_ci	struct ipv6hdr *ip6_hdr;
130062306a36Sopenharmony_ci	struct ipv6_opt_hdr *hop;
130162306a36Sopenharmony_ci	unsigned char buf[CALIPSO_MAX_BUFFER];
130262306a36Sopenharmony_ci	int len_delta, new_end, pad, payload;
130362306a36Sopenharmony_ci	unsigned int start, end;
130462306a36Sopenharmony_ci
130562306a36Sopenharmony_ci	ip6_hdr = ipv6_hdr(skb);
130662306a36Sopenharmony_ci	if (ip6_hdr->nexthdr == NEXTHDR_HOP) {
130762306a36Sopenharmony_ci		hop = (struct ipv6_opt_hdr *)(ip6_hdr + 1);
130862306a36Sopenharmony_ci		ret_val = calipso_opt_find(hop, &start, &end);
130962306a36Sopenharmony_ci		if (ret_val && ret_val != -ENOENT)
131062306a36Sopenharmony_ci			return ret_val;
131162306a36Sopenharmony_ci	} else {
131262306a36Sopenharmony_ci		start = 0;
131362306a36Sopenharmony_ci		end = 0;
131462306a36Sopenharmony_ci	}
131562306a36Sopenharmony_ci
131662306a36Sopenharmony_ci	memset(buf, 0, sizeof(buf));
131762306a36Sopenharmony_ci	ret_val = calipso_genopt(buf, start & 3, sizeof(buf), doi_def, secattr);
131862306a36Sopenharmony_ci	if (ret_val < 0)
131962306a36Sopenharmony_ci		return ret_val;
132062306a36Sopenharmony_ci
132162306a36Sopenharmony_ci	new_end = start + ret_val;
132262306a36Sopenharmony_ci	/* At this point new_end aligns to 4n, so (new_end & 4) pads to 8n */
132362306a36Sopenharmony_ci	pad = ((new_end & 4) + (end & 7)) & 7;
132462306a36Sopenharmony_ci	len_delta = new_end - (int)end + pad;
132562306a36Sopenharmony_ci	ret_val = skb_cow(skb, skb_headroom(skb) + len_delta);
132662306a36Sopenharmony_ci	if (ret_val < 0)
132762306a36Sopenharmony_ci		return ret_val;
132862306a36Sopenharmony_ci
132962306a36Sopenharmony_ci	ip6_hdr = ipv6_hdr(skb); /* Reset as skb_cow() may have moved it */
133062306a36Sopenharmony_ci
133162306a36Sopenharmony_ci	if (len_delta) {
133262306a36Sopenharmony_ci		if (len_delta > 0)
133362306a36Sopenharmony_ci			skb_push(skb, len_delta);
133462306a36Sopenharmony_ci		else
133562306a36Sopenharmony_ci			skb_pull(skb, -len_delta);
133662306a36Sopenharmony_ci		memmove((char *)ip6_hdr - len_delta, ip6_hdr,
133762306a36Sopenharmony_ci			sizeof(*ip6_hdr) + start);
133862306a36Sopenharmony_ci		skb_reset_network_header(skb);
133962306a36Sopenharmony_ci		ip6_hdr = ipv6_hdr(skb);
134062306a36Sopenharmony_ci		payload = ntohs(ip6_hdr->payload_len);
134162306a36Sopenharmony_ci		ip6_hdr->payload_len = htons(payload + len_delta);
134262306a36Sopenharmony_ci	}
134362306a36Sopenharmony_ci
134462306a36Sopenharmony_ci	hop = (struct ipv6_opt_hdr *)(ip6_hdr + 1);
134562306a36Sopenharmony_ci	if (start == 0) {
134662306a36Sopenharmony_ci		struct ipv6_opt_hdr *new_hop = (struct ipv6_opt_hdr *)buf;
134762306a36Sopenharmony_ci
134862306a36Sopenharmony_ci		new_hop->nexthdr = ip6_hdr->nexthdr;
134962306a36Sopenharmony_ci		new_hop->hdrlen = len_delta / 8 - 1;
135062306a36Sopenharmony_ci		ip6_hdr->nexthdr = NEXTHDR_HOP;
135162306a36Sopenharmony_ci	} else {
135262306a36Sopenharmony_ci		hop->hdrlen += len_delta / 8;
135362306a36Sopenharmony_ci	}
135462306a36Sopenharmony_ci	memcpy((char *)hop + start, buf + (start & 3), new_end - start);
135562306a36Sopenharmony_ci	calipso_pad_write((unsigned char *)hop, new_end, pad);
135662306a36Sopenharmony_ci
135762306a36Sopenharmony_ci	return 0;
135862306a36Sopenharmony_ci}
135962306a36Sopenharmony_ci
136062306a36Sopenharmony_ci/**
136162306a36Sopenharmony_ci * calipso_skbuff_delattr - Delete any CALIPSO options from a packet
136262306a36Sopenharmony_ci * @skb: the packet
136362306a36Sopenharmony_ci *
136462306a36Sopenharmony_ci * Description:
136562306a36Sopenharmony_ci * Removes any and all CALIPSO options from the given packet.  Returns zero on
136662306a36Sopenharmony_ci * success, negative values on failure.
136762306a36Sopenharmony_ci *
136862306a36Sopenharmony_ci */
136962306a36Sopenharmony_cistatic int calipso_skbuff_delattr(struct sk_buff *skb)
137062306a36Sopenharmony_ci{
137162306a36Sopenharmony_ci	int ret_val;
137262306a36Sopenharmony_ci	struct ipv6hdr *ip6_hdr;
137362306a36Sopenharmony_ci	struct ipv6_opt_hdr *old_hop;
137462306a36Sopenharmony_ci	u32 old_hop_len, start = 0, end = 0, delta, size, pad;
137562306a36Sopenharmony_ci
137662306a36Sopenharmony_ci	if (!calipso_skbuff_optptr(skb))
137762306a36Sopenharmony_ci		return 0;
137862306a36Sopenharmony_ci
137962306a36Sopenharmony_ci	/* since we are changing the packet we should make a copy */
138062306a36Sopenharmony_ci	ret_val = skb_cow(skb, skb_headroom(skb));
138162306a36Sopenharmony_ci	if (ret_val < 0)
138262306a36Sopenharmony_ci		return ret_val;
138362306a36Sopenharmony_ci
138462306a36Sopenharmony_ci	ip6_hdr = ipv6_hdr(skb);
138562306a36Sopenharmony_ci	old_hop = (struct ipv6_opt_hdr *)(ip6_hdr + 1);
138662306a36Sopenharmony_ci	old_hop_len = ipv6_optlen(old_hop);
138762306a36Sopenharmony_ci
138862306a36Sopenharmony_ci	ret_val = calipso_opt_find(old_hop, &start, &end);
138962306a36Sopenharmony_ci	if (ret_val)
139062306a36Sopenharmony_ci		return ret_val;
139162306a36Sopenharmony_ci
139262306a36Sopenharmony_ci	if (start == sizeof(*old_hop) && end == old_hop_len) {
139362306a36Sopenharmony_ci		/* There's no other option in the header so we delete
139462306a36Sopenharmony_ci		 * the whole thing. */
139562306a36Sopenharmony_ci		delta = old_hop_len;
139662306a36Sopenharmony_ci		size = sizeof(*ip6_hdr);
139762306a36Sopenharmony_ci		ip6_hdr->nexthdr = old_hop->nexthdr;
139862306a36Sopenharmony_ci	} else {
139962306a36Sopenharmony_ci		delta = (end - start) & ~7;
140062306a36Sopenharmony_ci		if (delta)
140162306a36Sopenharmony_ci			old_hop->hdrlen -= delta / 8;
140262306a36Sopenharmony_ci		pad = (end - start) & 7;
140362306a36Sopenharmony_ci		size = sizeof(*ip6_hdr) + start + pad;
140462306a36Sopenharmony_ci		calipso_pad_write((unsigned char *)old_hop, start, pad);
140562306a36Sopenharmony_ci	}
140662306a36Sopenharmony_ci
140762306a36Sopenharmony_ci	if (delta) {
140862306a36Sopenharmony_ci		skb_pull(skb, delta);
140962306a36Sopenharmony_ci		memmove((char *)ip6_hdr + delta, ip6_hdr, size);
141062306a36Sopenharmony_ci		skb_reset_network_header(skb);
141162306a36Sopenharmony_ci	}
141262306a36Sopenharmony_ci
141362306a36Sopenharmony_ci	return 0;
141462306a36Sopenharmony_ci}
141562306a36Sopenharmony_ci
141662306a36Sopenharmony_cistatic const struct netlbl_calipso_ops ops = {
141762306a36Sopenharmony_ci	.doi_add          = calipso_doi_add,
141862306a36Sopenharmony_ci	.doi_free         = calipso_doi_free,
141962306a36Sopenharmony_ci	.doi_remove       = calipso_doi_remove,
142062306a36Sopenharmony_ci	.doi_getdef       = calipso_doi_getdef,
142162306a36Sopenharmony_ci	.doi_putdef       = calipso_doi_putdef,
142262306a36Sopenharmony_ci	.doi_walk         = calipso_doi_walk,
142362306a36Sopenharmony_ci	.sock_getattr     = calipso_sock_getattr,
142462306a36Sopenharmony_ci	.sock_setattr     = calipso_sock_setattr,
142562306a36Sopenharmony_ci	.sock_delattr     = calipso_sock_delattr,
142662306a36Sopenharmony_ci	.req_setattr      = calipso_req_setattr,
142762306a36Sopenharmony_ci	.req_delattr      = calipso_req_delattr,
142862306a36Sopenharmony_ci	.opt_getattr      = calipso_opt_getattr,
142962306a36Sopenharmony_ci	.skbuff_optptr    = calipso_skbuff_optptr,
143062306a36Sopenharmony_ci	.skbuff_setattr   = calipso_skbuff_setattr,
143162306a36Sopenharmony_ci	.skbuff_delattr   = calipso_skbuff_delattr,
143262306a36Sopenharmony_ci	.cache_invalidate = calipso_cache_invalidate,
143362306a36Sopenharmony_ci	.cache_add        = calipso_cache_add
143462306a36Sopenharmony_ci};
143562306a36Sopenharmony_ci
143662306a36Sopenharmony_ci/**
143762306a36Sopenharmony_ci * calipso_init - Initialize the CALIPSO module
143862306a36Sopenharmony_ci *
143962306a36Sopenharmony_ci * Description:
144062306a36Sopenharmony_ci * Initialize the CALIPSO module and prepare it for use.  Returns zero on
144162306a36Sopenharmony_ci * success and negative values on failure.
144262306a36Sopenharmony_ci *
144362306a36Sopenharmony_ci */
144462306a36Sopenharmony_ciint __init calipso_init(void)
144562306a36Sopenharmony_ci{
144662306a36Sopenharmony_ci	int ret_val;
144762306a36Sopenharmony_ci
144862306a36Sopenharmony_ci	ret_val = calipso_cache_init();
144962306a36Sopenharmony_ci	if (!ret_val)
145062306a36Sopenharmony_ci		netlbl_calipso_ops_register(&ops);
145162306a36Sopenharmony_ci	return ret_val;
145262306a36Sopenharmony_ci}
145362306a36Sopenharmony_ci
145462306a36Sopenharmony_civoid calipso_exit(void)
145562306a36Sopenharmony_ci{
145662306a36Sopenharmony_ci	netlbl_calipso_ops_register(NULL);
145762306a36Sopenharmony_ci	calipso_cache_invalidate();
145862306a36Sopenharmony_ci	kfree(calipso_cache);
145962306a36Sopenharmony_ci}
1460