xref: /openbmc/linux/net/ipv6/calipso.c (revision ceba1832b1b2da0149c51de62a847c00bca1677a)
1cb72d382SHuw Davies /*
2cb72d382SHuw Davies  * CALIPSO - Common Architecture Label IPv6 Security Option
3cb72d382SHuw Davies  *
4cb72d382SHuw Davies  * This is an implementation of the CALIPSO protocol as specified in
5cb72d382SHuw Davies  * RFC 5570.
6cb72d382SHuw Davies  *
7cb72d382SHuw Davies  * Authors: Paul Moore <paul.moore@hp.com>
8cb72d382SHuw Davies  *          Huw Davies <huw@codeweavers.com>
9cb72d382SHuw Davies  *
10cb72d382SHuw Davies  */
11cb72d382SHuw Davies 
12cb72d382SHuw Davies /* (c) Copyright Hewlett-Packard Development Company, L.P., 2006, 2008
13cb72d382SHuw Davies  * (c) Copyright Huw Davies <huw@codeweavers.com>, 2015
14cb72d382SHuw Davies  *
15cb72d382SHuw Davies  * This program is free software;  you can redistribute it and/or modify
16cb72d382SHuw Davies  * it under the terms of the GNU General Public License as published by
17cb72d382SHuw Davies  * the Free Software Foundation; either version 2 of the License, or
18cb72d382SHuw Davies  * (at your option) any later version.
19cb72d382SHuw Davies  *
20cb72d382SHuw Davies  * This program is distributed in the hope that it will be useful,
21cb72d382SHuw Davies  * but WITHOUT ANY WARRANTY;  without even the implied warranty of
22cb72d382SHuw Davies  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See
23cb72d382SHuw Davies  * the GNU General Public License for more details.
24cb72d382SHuw Davies  *
25cb72d382SHuw Davies  * You should have received a copy of the GNU General Public License
26cb72d382SHuw Davies  * along with this program;  if not, see <http://www.gnu.org/licenses/>.
27cb72d382SHuw Davies  *
28cb72d382SHuw Davies  */
29cb72d382SHuw Davies 
30cb72d382SHuw Davies #include <linux/init.h>
31cb72d382SHuw Davies #include <linux/types.h>
32cb72d382SHuw Davies #include <linux/rcupdate.h>
33cb72d382SHuw Davies #include <linux/list.h>
34cb72d382SHuw Davies #include <linux/spinlock.h>
35cb72d382SHuw Davies #include <linux/string.h>
36cb72d382SHuw Davies #include <linux/jhash.h>
37cb72d382SHuw Davies #include <linux/audit.h>
38cb72d382SHuw Davies #include <linux/slab.h>
39cb72d382SHuw Davies #include <net/ip.h>
40cb72d382SHuw Davies #include <net/icmp.h>
41cb72d382SHuw Davies #include <net/tcp.h>
42cb72d382SHuw Davies #include <net/netlabel.h>
43cb72d382SHuw Davies #include <net/calipso.h>
44cb72d382SHuw Davies #include <linux/atomic.h>
45cb72d382SHuw Davies #include <linux/bug.h>
46cb72d382SHuw Davies #include <asm/unaligned.h>
47*ceba1832SHuw Davies #include <linux/crc-ccitt.h>
48*ceba1832SHuw Davies 
49*ceba1832SHuw Davies /* Maximium size of the calipso option including
50*ceba1832SHuw Davies  * the two-byte TLV header.
51*ceba1832SHuw Davies  */
52*ceba1832SHuw Davies #define CALIPSO_OPT_LEN_MAX (2 + 252)
53*ceba1832SHuw Davies 
54*ceba1832SHuw Davies /* Size of the minimum calipso option including
55*ceba1832SHuw Davies  * the two-byte TLV header.
56*ceba1832SHuw Davies  */
57*ceba1832SHuw Davies #define CALIPSO_HDR_LEN (2 + 8)
58*ceba1832SHuw Davies 
59*ceba1832SHuw Davies /* Maximium size of the calipso option including
60*ceba1832SHuw Davies  * the two-byte TLV header and upto 3 bytes of
61*ceba1832SHuw Davies  * leading pad and 7 bytes of trailing pad.
62*ceba1832SHuw Davies  */
63*ceba1832SHuw Davies #define CALIPSO_OPT_LEN_MAX_WITH_PAD (3 + CALIPSO_OPT_LEN_MAX + 7)
64*ceba1832SHuw Davies 
65cb72d382SHuw Davies 
66cb72d382SHuw Davies /* List of available DOI definitions */
67cb72d382SHuw Davies static DEFINE_SPINLOCK(calipso_doi_list_lock);
68cb72d382SHuw Davies static LIST_HEAD(calipso_doi_list);
69cb72d382SHuw Davies 
70cb72d382SHuw Davies /* DOI List Functions
71cb72d382SHuw Davies  */
72cb72d382SHuw Davies 
73cb72d382SHuw Davies /**
74cb72d382SHuw Davies  * calipso_doi_search - Searches for a DOI definition
75cb72d382SHuw Davies  * @doi: the DOI to search for
76cb72d382SHuw Davies  *
77cb72d382SHuw Davies  * Description:
78cb72d382SHuw Davies  * Search the DOI definition list for a DOI definition with a DOI value that
79cb72d382SHuw Davies  * matches @doi.  The caller is responsible for calling rcu_read_[un]lock().
80cb72d382SHuw Davies  * Returns a pointer to the DOI definition on success and NULL on failure.
81cb72d382SHuw Davies  */
82cb72d382SHuw Davies static struct calipso_doi *calipso_doi_search(u32 doi)
83cb72d382SHuw Davies {
84cb72d382SHuw Davies 	struct calipso_doi *iter;
85cb72d382SHuw Davies 
86cb72d382SHuw Davies 	list_for_each_entry_rcu(iter, &calipso_doi_list, list)
87cb72d382SHuw Davies 		if (iter->doi == doi && atomic_read(&iter->refcount))
88cb72d382SHuw Davies 			return iter;
89cb72d382SHuw Davies 	return NULL;
90cb72d382SHuw Davies }
91cb72d382SHuw Davies 
92cb72d382SHuw Davies /**
93cb72d382SHuw Davies  * calipso_doi_add - Add a new DOI to the CALIPSO protocol engine
94cb72d382SHuw Davies  * @doi_def: the DOI structure
95cb72d382SHuw Davies  * @audit_info: NetLabel audit information
96cb72d382SHuw Davies  *
97cb72d382SHuw Davies  * Description:
98cb72d382SHuw Davies  * The caller defines a new DOI for use by the CALIPSO engine and calls this
99cb72d382SHuw Davies  * function to add it to the list of acceptable domains.  The caller must
100cb72d382SHuw Davies  * ensure that the mapping table specified in @doi_def->map meets all of the
101cb72d382SHuw Davies  * requirements of the mapping type (see calipso.h for details).  Returns
102cb72d382SHuw Davies  * zero on success and non-zero on failure.
103cb72d382SHuw Davies  *
104cb72d382SHuw Davies  */
105cb72d382SHuw Davies static int calipso_doi_add(struct calipso_doi *doi_def,
106cb72d382SHuw Davies 			   struct netlbl_audit *audit_info)
107cb72d382SHuw Davies {
108cb72d382SHuw Davies 	int ret_val = -EINVAL;
109cb72d382SHuw Davies 	u32 doi;
110cb72d382SHuw Davies 	u32 doi_type;
111cb72d382SHuw Davies 	struct audit_buffer *audit_buf;
112cb72d382SHuw Davies 
113cb72d382SHuw Davies 	doi = doi_def->doi;
114cb72d382SHuw Davies 	doi_type = doi_def->type;
115cb72d382SHuw Davies 
116cb72d382SHuw Davies 	if (doi_def->doi == CALIPSO_DOI_UNKNOWN)
117cb72d382SHuw Davies 		goto doi_add_return;
118cb72d382SHuw Davies 
119cb72d382SHuw Davies 	atomic_set(&doi_def->refcount, 1);
120cb72d382SHuw Davies 
121cb72d382SHuw Davies 	spin_lock(&calipso_doi_list_lock);
122cb72d382SHuw Davies 	if (calipso_doi_search(doi_def->doi)) {
123cb72d382SHuw Davies 		spin_unlock(&calipso_doi_list_lock);
124cb72d382SHuw Davies 		ret_val = -EEXIST;
125cb72d382SHuw Davies 		goto doi_add_return;
126cb72d382SHuw Davies 	}
127cb72d382SHuw Davies 	list_add_tail_rcu(&doi_def->list, &calipso_doi_list);
128cb72d382SHuw Davies 	spin_unlock(&calipso_doi_list_lock);
129cb72d382SHuw Davies 	ret_val = 0;
130cb72d382SHuw Davies 
131cb72d382SHuw Davies doi_add_return:
132cb72d382SHuw Davies 	audit_buf = netlbl_audit_start(AUDIT_MAC_CALIPSO_ADD, audit_info);
133cb72d382SHuw Davies 	if (audit_buf) {
134cb72d382SHuw Davies 		const char *type_str;
135cb72d382SHuw Davies 
136cb72d382SHuw Davies 		switch (doi_type) {
137cb72d382SHuw Davies 		case CALIPSO_MAP_PASS:
138cb72d382SHuw Davies 			type_str = "pass";
139cb72d382SHuw Davies 			break;
140cb72d382SHuw Davies 		default:
141cb72d382SHuw Davies 			type_str = "(unknown)";
142cb72d382SHuw Davies 		}
143cb72d382SHuw Davies 		audit_log_format(audit_buf,
144cb72d382SHuw Davies 				 " calipso_doi=%u calipso_type=%s res=%u",
145cb72d382SHuw Davies 				 doi, type_str, ret_val == 0 ? 1 : 0);
146cb72d382SHuw Davies 		audit_log_end(audit_buf);
147cb72d382SHuw Davies 	}
148cb72d382SHuw Davies 
149cb72d382SHuw Davies 	return ret_val;
150cb72d382SHuw Davies }
151cb72d382SHuw Davies 
152cb72d382SHuw Davies /**
153cb72d382SHuw Davies  * calipso_doi_free - Frees a DOI definition
154cb72d382SHuw Davies  * @doi_def: the DOI definition
155cb72d382SHuw Davies  *
156cb72d382SHuw Davies  * Description:
157cb72d382SHuw Davies  * This function frees all of the memory associated with a DOI definition.
158cb72d382SHuw Davies  *
159cb72d382SHuw Davies  */
160cb72d382SHuw Davies static void calipso_doi_free(struct calipso_doi *doi_def)
161cb72d382SHuw Davies {
162cb72d382SHuw Davies 	kfree(doi_def);
163cb72d382SHuw Davies }
164cb72d382SHuw Davies 
165a5e34490SHuw Davies /**
166a5e34490SHuw Davies  * calipso_doi_free_rcu - Frees a DOI definition via the RCU pointer
167a5e34490SHuw Davies  * @entry: the entry's RCU field
168a5e34490SHuw Davies  *
169a5e34490SHuw Davies  * Description:
170a5e34490SHuw Davies  * This function is designed to be used as a callback to the call_rcu()
171a5e34490SHuw Davies  * function so that the memory allocated to the DOI definition can be released
172a5e34490SHuw Davies  * safely.
173a5e34490SHuw Davies  *
174a5e34490SHuw Davies  */
175a5e34490SHuw Davies static void calipso_doi_free_rcu(struct rcu_head *entry)
176a5e34490SHuw Davies {
177a5e34490SHuw Davies 	struct calipso_doi *doi_def;
178a5e34490SHuw Davies 
179a5e34490SHuw Davies 	doi_def = container_of(entry, struct calipso_doi, rcu);
180a5e34490SHuw Davies 	calipso_doi_free(doi_def);
181a5e34490SHuw Davies }
182a5e34490SHuw Davies 
183a5e34490SHuw Davies /**
184d7cce015SHuw Davies  * calipso_doi_remove - Remove an existing DOI from the CALIPSO protocol engine
185d7cce015SHuw Davies  * @doi: the DOI value
186d7cce015SHuw Davies  * @audit_secid: the LSM secid to use in the audit message
187d7cce015SHuw Davies  *
188d7cce015SHuw Davies  * Description:
189d7cce015SHuw Davies  * Removes a DOI definition from the CALIPSO engine.  The NetLabel routines will
190d7cce015SHuw Davies  * be called to release their own LSM domain mappings as well as our own
191d7cce015SHuw Davies  * domain list.  Returns zero on success and negative values on failure.
192d7cce015SHuw Davies  *
193d7cce015SHuw Davies  */
194d7cce015SHuw Davies static int calipso_doi_remove(u32 doi, struct netlbl_audit *audit_info)
195d7cce015SHuw Davies {
196d7cce015SHuw Davies 	int ret_val;
197d7cce015SHuw Davies 	struct calipso_doi *doi_def;
198d7cce015SHuw Davies 	struct audit_buffer *audit_buf;
199d7cce015SHuw Davies 
200d7cce015SHuw Davies 	spin_lock(&calipso_doi_list_lock);
201d7cce015SHuw Davies 	doi_def = calipso_doi_search(doi);
202d7cce015SHuw Davies 	if (!doi_def) {
203d7cce015SHuw Davies 		spin_unlock(&calipso_doi_list_lock);
204d7cce015SHuw Davies 		ret_val = -ENOENT;
205d7cce015SHuw Davies 		goto doi_remove_return;
206d7cce015SHuw Davies 	}
207d7cce015SHuw Davies 	if (!atomic_dec_and_test(&doi_def->refcount)) {
208d7cce015SHuw Davies 		spin_unlock(&calipso_doi_list_lock);
209d7cce015SHuw Davies 		ret_val = -EBUSY;
210d7cce015SHuw Davies 		goto doi_remove_return;
211d7cce015SHuw Davies 	}
212d7cce015SHuw Davies 	list_del_rcu(&doi_def->list);
213d7cce015SHuw Davies 	spin_unlock(&calipso_doi_list_lock);
214d7cce015SHuw Davies 
215d7cce015SHuw Davies 	call_rcu(&doi_def->rcu, calipso_doi_free_rcu);
216d7cce015SHuw Davies 	ret_val = 0;
217d7cce015SHuw Davies 
218d7cce015SHuw Davies doi_remove_return:
219d7cce015SHuw Davies 	audit_buf = netlbl_audit_start(AUDIT_MAC_CALIPSO_DEL, audit_info);
220d7cce015SHuw Davies 	if (audit_buf) {
221d7cce015SHuw Davies 		audit_log_format(audit_buf,
222d7cce015SHuw Davies 				 " calipso_doi=%u res=%u",
223d7cce015SHuw Davies 				 doi, ret_val == 0 ? 1 : 0);
224d7cce015SHuw Davies 		audit_log_end(audit_buf);
225d7cce015SHuw Davies 	}
226d7cce015SHuw Davies 
227d7cce015SHuw Davies 	return ret_val;
228d7cce015SHuw Davies }
229d7cce015SHuw Davies 
230d7cce015SHuw Davies /**
231a5e34490SHuw Davies  * calipso_doi_getdef - Returns a reference to a valid DOI definition
232a5e34490SHuw Davies  * @doi: the DOI value
233a5e34490SHuw Davies  *
234a5e34490SHuw Davies  * Description:
235a5e34490SHuw Davies  * Searches for a valid DOI definition and if one is found it is returned to
236a5e34490SHuw Davies  * the caller.  Otherwise NULL is returned.  The caller must ensure that
237a5e34490SHuw Davies  * calipso_doi_putdef() is called when the caller is done.
238a5e34490SHuw Davies  *
239a5e34490SHuw Davies  */
240a5e34490SHuw Davies static struct calipso_doi *calipso_doi_getdef(u32 doi)
241a5e34490SHuw Davies {
242a5e34490SHuw Davies 	struct calipso_doi *doi_def;
243a5e34490SHuw Davies 
244a5e34490SHuw Davies 	rcu_read_lock();
245a5e34490SHuw Davies 	doi_def = calipso_doi_search(doi);
246a5e34490SHuw Davies 	if (!doi_def)
247a5e34490SHuw Davies 		goto doi_getdef_return;
248a5e34490SHuw Davies 	if (!atomic_inc_not_zero(&doi_def->refcount))
249a5e34490SHuw Davies 		doi_def = NULL;
250a5e34490SHuw Davies 
251a5e34490SHuw Davies doi_getdef_return:
252a5e34490SHuw Davies 	rcu_read_unlock();
253a5e34490SHuw Davies 	return doi_def;
254a5e34490SHuw Davies }
255a5e34490SHuw Davies 
256a5e34490SHuw Davies /**
257a5e34490SHuw Davies  * calipso_doi_putdef - Releases a reference for the given DOI definition
258a5e34490SHuw Davies  * @doi_def: the DOI definition
259a5e34490SHuw Davies  *
260a5e34490SHuw Davies  * Description:
261a5e34490SHuw Davies  * Releases a DOI definition reference obtained from calipso_doi_getdef().
262a5e34490SHuw Davies  *
263a5e34490SHuw Davies  */
264a5e34490SHuw Davies static void calipso_doi_putdef(struct calipso_doi *doi_def)
265a5e34490SHuw Davies {
266a5e34490SHuw Davies 	if (!doi_def)
267a5e34490SHuw Davies 		return;
268a5e34490SHuw Davies 
269a5e34490SHuw Davies 	if (!atomic_dec_and_test(&doi_def->refcount))
270a5e34490SHuw Davies 		return;
271a5e34490SHuw Davies 	spin_lock(&calipso_doi_list_lock);
272a5e34490SHuw Davies 	list_del_rcu(&doi_def->list);
273a5e34490SHuw Davies 	spin_unlock(&calipso_doi_list_lock);
274a5e34490SHuw Davies 
275a5e34490SHuw Davies 	call_rcu(&doi_def->rcu, calipso_doi_free_rcu);
276a5e34490SHuw Davies }
277a5e34490SHuw Davies 
278e1ce69dfSHuw Davies /**
279e1ce69dfSHuw Davies  * calipso_doi_walk - Iterate through the DOI definitions
280e1ce69dfSHuw Davies  * @skip_cnt: skip past this number of DOI definitions, updated
281e1ce69dfSHuw Davies  * @callback: callback for each DOI definition
282e1ce69dfSHuw Davies  * @cb_arg: argument for the callback function
283e1ce69dfSHuw Davies  *
284e1ce69dfSHuw Davies  * Description:
285e1ce69dfSHuw Davies  * Iterate over the DOI definition list, skipping the first @skip_cnt entries.
286e1ce69dfSHuw Davies  * For each entry call @callback, if @callback returns a negative value stop
287e1ce69dfSHuw Davies  * 'walking' through the list and return.  Updates the value in @skip_cnt upon
288e1ce69dfSHuw Davies  * return.  Returns zero on success, negative values on failure.
289e1ce69dfSHuw Davies  *
290e1ce69dfSHuw Davies  */
291e1ce69dfSHuw Davies static int calipso_doi_walk(u32 *skip_cnt,
292e1ce69dfSHuw Davies 			    int (*callback)(struct calipso_doi *doi_def,
293e1ce69dfSHuw Davies 					    void *arg),
294e1ce69dfSHuw Davies 			    void *cb_arg)
295e1ce69dfSHuw Davies {
296e1ce69dfSHuw Davies 	int ret_val = -ENOENT;
297e1ce69dfSHuw Davies 	u32 doi_cnt = 0;
298e1ce69dfSHuw Davies 	struct calipso_doi *iter_doi;
299e1ce69dfSHuw Davies 
300e1ce69dfSHuw Davies 	rcu_read_lock();
301e1ce69dfSHuw Davies 	list_for_each_entry_rcu(iter_doi, &calipso_doi_list, list)
302e1ce69dfSHuw Davies 		if (atomic_read(&iter_doi->refcount) > 0) {
303e1ce69dfSHuw Davies 			if (doi_cnt++ < *skip_cnt)
304e1ce69dfSHuw Davies 				continue;
305e1ce69dfSHuw Davies 			ret_val = callback(iter_doi, cb_arg);
306e1ce69dfSHuw Davies 			if (ret_val < 0) {
307e1ce69dfSHuw Davies 				doi_cnt--;
308e1ce69dfSHuw Davies 				goto doi_walk_return;
309e1ce69dfSHuw Davies 			}
310e1ce69dfSHuw Davies 		}
311e1ce69dfSHuw Davies 
312e1ce69dfSHuw Davies doi_walk_return:
313e1ce69dfSHuw Davies 	rcu_read_unlock();
314e1ce69dfSHuw Davies 	*skip_cnt = doi_cnt;
315e1ce69dfSHuw Davies 	return ret_val;
316e1ce69dfSHuw Davies }
317e1ce69dfSHuw Davies 
318*ceba1832SHuw Davies /**
319*ceba1832SHuw Davies  * calipso_map_cat_hton - Perform a category mapping from host to network
320*ceba1832SHuw Davies  * @doi_def: the DOI definition
321*ceba1832SHuw Davies  * @secattr: the security attributes
322*ceba1832SHuw Davies  * @net_cat: the zero'd out category bitmap in network/CALIPSO format
323*ceba1832SHuw Davies  * @net_cat_len: the length of the CALIPSO bitmap in bytes
324*ceba1832SHuw Davies  *
325*ceba1832SHuw Davies  * Description:
326*ceba1832SHuw Davies  * Perform a label mapping to translate a local MLS category bitmap to the
327*ceba1832SHuw Davies  * correct CALIPSO bitmap using the given DOI definition.  Returns the minimum
328*ceba1832SHuw Davies  * size in bytes of the network bitmap on success, negative values otherwise.
329*ceba1832SHuw Davies  *
330*ceba1832SHuw Davies  */
331*ceba1832SHuw Davies static int calipso_map_cat_hton(const struct calipso_doi *doi_def,
332*ceba1832SHuw Davies 				const struct netlbl_lsm_secattr *secattr,
333*ceba1832SHuw Davies 				unsigned char *net_cat,
334*ceba1832SHuw Davies 				u32 net_cat_len)
335*ceba1832SHuw Davies {
336*ceba1832SHuw Davies 	int spot = -1;
337*ceba1832SHuw Davies 	u32 net_spot_max = 0;
338*ceba1832SHuw Davies 	u32 net_clen_bits = net_cat_len * 8;
339*ceba1832SHuw Davies 
340*ceba1832SHuw Davies 	for (;;) {
341*ceba1832SHuw Davies 		spot = netlbl_catmap_walk(secattr->attr.mls.cat,
342*ceba1832SHuw Davies 					  spot + 1);
343*ceba1832SHuw Davies 		if (spot < 0)
344*ceba1832SHuw Davies 			break;
345*ceba1832SHuw Davies 		if (spot >= net_clen_bits)
346*ceba1832SHuw Davies 			return -ENOSPC;
347*ceba1832SHuw Davies 		netlbl_bitmap_setbit(net_cat, spot, 1);
348*ceba1832SHuw Davies 
349*ceba1832SHuw Davies 		if (spot > net_spot_max)
350*ceba1832SHuw Davies 			net_spot_max = spot;
351*ceba1832SHuw Davies 	}
352*ceba1832SHuw Davies 
353*ceba1832SHuw Davies 	return (net_spot_max / 32 + 1) * 4;
354*ceba1832SHuw Davies }
355*ceba1832SHuw Davies 
356*ceba1832SHuw Davies /**
357*ceba1832SHuw Davies  * calipso_map_cat_ntoh - Perform a category mapping from network to host
358*ceba1832SHuw Davies  * @doi_def: the DOI definition
359*ceba1832SHuw Davies  * @net_cat: the category bitmap in network/CALIPSO format
360*ceba1832SHuw Davies  * @net_cat_len: the length of the CALIPSO bitmap in bytes
361*ceba1832SHuw Davies  * @secattr: the security attributes
362*ceba1832SHuw Davies  *
363*ceba1832SHuw Davies  * Description:
364*ceba1832SHuw Davies  * Perform a label mapping to translate a CALIPSO bitmap to the correct local
365*ceba1832SHuw Davies  * MLS category bitmap using the given DOI definition.  Returns zero on
366*ceba1832SHuw Davies  * success, negative values on failure.
367*ceba1832SHuw Davies  *
368*ceba1832SHuw Davies  */
369*ceba1832SHuw Davies static int calipso_map_cat_ntoh(const struct calipso_doi *doi_def,
370*ceba1832SHuw Davies 				const unsigned char *net_cat,
371*ceba1832SHuw Davies 				u32 net_cat_len,
372*ceba1832SHuw Davies 				struct netlbl_lsm_secattr *secattr)
373*ceba1832SHuw Davies {
374*ceba1832SHuw Davies 	int ret_val;
375*ceba1832SHuw Davies 	int spot = -1;
376*ceba1832SHuw Davies 	u32 net_clen_bits = net_cat_len * 8;
377*ceba1832SHuw Davies 
378*ceba1832SHuw Davies 	for (;;) {
379*ceba1832SHuw Davies 		spot = netlbl_bitmap_walk(net_cat,
380*ceba1832SHuw Davies 					  net_clen_bits,
381*ceba1832SHuw Davies 					  spot + 1,
382*ceba1832SHuw Davies 					  1);
383*ceba1832SHuw Davies 		if (spot < 0) {
384*ceba1832SHuw Davies 			if (spot == -2)
385*ceba1832SHuw Davies 				return -EFAULT;
386*ceba1832SHuw Davies 			return 0;
387*ceba1832SHuw Davies 		}
388*ceba1832SHuw Davies 
389*ceba1832SHuw Davies 		ret_val = netlbl_catmap_setbit(&secattr->attr.mls.cat,
390*ceba1832SHuw Davies 					       spot,
391*ceba1832SHuw Davies 					       GFP_ATOMIC);
392*ceba1832SHuw Davies 		if (ret_val != 0)
393*ceba1832SHuw Davies 			return ret_val;
394*ceba1832SHuw Davies 	}
395*ceba1832SHuw Davies 
396*ceba1832SHuw Davies 	return -EINVAL;
397*ceba1832SHuw Davies }
398*ceba1832SHuw Davies 
399*ceba1832SHuw Davies /**
400*ceba1832SHuw Davies  * calipso_pad_write - Writes pad bytes in TLV format
401*ceba1832SHuw Davies  * @buf: the buffer
402*ceba1832SHuw Davies  * @offset: offset from start of buffer to write padding
403*ceba1832SHuw Davies  * @count: number of pad bytes to write
404*ceba1832SHuw Davies  *
405*ceba1832SHuw Davies  * Description:
406*ceba1832SHuw Davies  * Write @count bytes of TLV padding into @buffer starting at offset @offset.
407*ceba1832SHuw Davies  * @count should be less than 8 - see RFC 4942.
408*ceba1832SHuw Davies  *
409*ceba1832SHuw Davies  */
410*ceba1832SHuw Davies static int calipso_pad_write(unsigned char *buf, unsigned int offset,
411*ceba1832SHuw Davies 			     unsigned int count)
412*ceba1832SHuw Davies {
413*ceba1832SHuw Davies 	if (WARN_ON_ONCE(count >= 8))
414*ceba1832SHuw Davies 		return -EINVAL;
415*ceba1832SHuw Davies 
416*ceba1832SHuw Davies 	switch (count) {
417*ceba1832SHuw Davies 	case 0:
418*ceba1832SHuw Davies 		break;
419*ceba1832SHuw Davies 	case 1:
420*ceba1832SHuw Davies 		buf[offset] = IPV6_TLV_PAD1;
421*ceba1832SHuw Davies 		break;
422*ceba1832SHuw Davies 	default:
423*ceba1832SHuw Davies 		buf[offset] = IPV6_TLV_PADN;
424*ceba1832SHuw Davies 		buf[offset + 1] = count - 2;
425*ceba1832SHuw Davies 		if (count > 2)
426*ceba1832SHuw Davies 			memset(buf + offset + 2, 0, count - 2);
427*ceba1832SHuw Davies 		break;
428*ceba1832SHuw Davies 	}
429*ceba1832SHuw Davies 	return 0;
430*ceba1832SHuw Davies }
431*ceba1832SHuw Davies 
432*ceba1832SHuw Davies /**
433*ceba1832SHuw Davies  * calipso_genopt - Generate a CALIPSO option
434*ceba1832SHuw Davies  * @buf: the option buffer
435*ceba1832SHuw Davies  * @start: offset from which to write
436*ceba1832SHuw Davies  * @buf_len: the size of opt_buf
437*ceba1832SHuw Davies  * @doi_def: the CALIPSO DOI to use
438*ceba1832SHuw Davies  * @secattr: the security attributes
439*ceba1832SHuw Davies  *
440*ceba1832SHuw Davies  * Description:
441*ceba1832SHuw Davies  * Generate a CALIPSO option using the DOI definition and security attributes
442*ceba1832SHuw Davies  * passed to the function. This also generates upto three bytes of leading
443*ceba1832SHuw Davies  * padding that ensures that the option is 4n + 2 aligned.  It returns the
444*ceba1832SHuw Davies  * number of bytes written (including any initial padding).
445*ceba1832SHuw Davies  */
446*ceba1832SHuw Davies static int calipso_genopt(unsigned char *buf, u32 start, u32 buf_len,
447*ceba1832SHuw Davies 			  const struct calipso_doi *doi_def,
448*ceba1832SHuw Davies 			  const struct netlbl_lsm_secattr *secattr)
449*ceba1832SHuw Davies {
450*ceba1832SHuw Davies 	int ret_val;
451*ceba1832SHuw Davies 	u32 len, pad;
452*ceba1832SHuw Davies 	u16 crc;
453*ceba1832SHuw Davies 	static const unsigned char padding[4] = {2, 1, 0, 3};
454*ceba1832SHuw Davies 	unsigned char *calipso;
455*ceba1832SHuw Davies 
456*ceba1832SHuw Davies 	/* CALIPSO has 4n + 2 alignment */
457*ceba1832SHuw Davies 	pad = padding[start & 3];
458*ceba1832SHuw Davies 	if (buf_len <= start + pad + CALIPSO_HDR_LEN)
459*ceba1832SHuw Davies 		return -ENOSPC;
460*ceba1832SHuw Davies 
461*ceba1832SHuw Davies 	if ((secattr->flags & NETLBL_SECATTR_MLS_LVL) == 0)
462*ceba1832SHuw Davies 		return -EPERM;
463*ceba1832SHuw Davies 
464*ceba1832SHuw Davies 	len = CALIPSO_HDR_LEN;
465*ceba1832SHuw Davies 
466*ceba1832SHuw Davies 	if (secattr->flags & NETLBL_SECATTR_MLS_CAT) {
467*ceba1832SHuw Davies 		ret_val = calipso_map_cat_hton(doi_def,
468*ceba1832SHuw Davies 					       secattr,
469*ceba1832SHuw Davies 					       buf + start + pad + len,
470*ceba1832SHuw Davies 					       buf_len - start - pad - len);
471*ceba1832SHuw Davies 		if (ret_val < 0)
472*ceba1832SHuw Davies 			return ret_val;
473*ceba1832SHuw Davies 		len += ret_val;
474*ceba1832SHuw Davies 	}
475*ceba1832SHuw Davies 
476*ceba1832SHuw Davies 	calipso_pad_write(buf, start, pad);
477*ceba1832SHuw Davies 	calipso = buf + start + pad;
478*ceba1832SHuw Davies 
479*ceba1832SHuw Davies 	calipso[0] = IPV6_TLV_CALIPSO;
480*ceba1832SHuw Davies 	calipso[1] = len - 2;
481*ceba1832SHuw Davies 	*(__be32 *)(calipso + 2) = htonl(doi_def->doi);
482*ceba1832SHuw Davies 	calipso[6] = (len - CALIPSO_HDR_LEN) / 4;
483*ceba1832SHuw Davies 	calipso[7] = secattr->attr.mls.lvl,
484*ceba1832SHuw Davies 	crc = ~crc_ccitt(0xffff, calipso, len);
485*ceba1832SHuw Davies 	calipso[8] = crc & 0xff;
486*ceba1832SHuw Davies 	calipso[9] = (crc >> 8) & 0xff;
487*ceba1832SHuw Davies 	return pad + len;
488*ceba1832SHuw Davies }
489*ceba1832SHuw Davies 
490*ceba1832SHuw Davies /* Hop-by-hop hdr helper functions
491*ceba1832SHuw Davies  */
492*ceba1832SHuw Davies 
493*ceba1832SHuw Davies /**
494*ceba1832SHuw Davies  * calipso_opt_update - Replaces socket's hop options with a new set
495*ceba1832SHuw Davies  * @sk: the socket
496*ceba1832SHuw Davies  * @hop: new hop options
497*ceba1832SHuw Davies  *
498*ceba1832SHuw Davies  * Description:
499*ceba1832SHuw Davies  * Replaces @sk's hop options with @hop.  @hop may be NULL to leave
500*ceba1832SHuw Davies  * the socket with no hop options.
501*ceba1832SHuw Davies  *
502*ceba1832SHuw Davies  */
503*ceba1832SHuw Davies static int calipso_opt_update(struct sock *sk, struct ipv6_opt_hdr *hop)
504*ceba1832SHuw Davies {
505*ceba1832SHuw Davies 	struct ipv6_txoptions *old = txopt_get(inet6_sk(sk)), *txopts;
506*ceba1832SHuw Davies 
507*ceba1832SHuw Davies 	txopts = ipv6_renew_options_kern(sk, old, IPV6_HOPOPTS,
508*ceba1832SHuw Davies 					 hop, hop ? ipv6_optlen(hop) : 0);
509*ceba1832SHuw Davies 	txopt_put(old);
510*ceba1832SHuw Davies 	if (IS_ERR(txopts))
511*ceba1832SHuw Davies 		return PTR_ERR(txopts);
512*ceba1832SHuw Davies 
513*ceba1832SHuw Davies 	txopts = ipv6_update_options(sk, txopts);
514*ceba1832SHuw Davies 	if (txopts) {
515*ceba1832SHuw Davies 		atomic_sub(txopts->tot_len, &sk->sk_omem_alloc);
516*ceba1832SHuw Davies 		txopt_put(txopts);
517*ceba1832SHuw Davies 	}
518*ceba1832SHuw Davies 
519*ceba1832SHuw Davies 	return 0;
520*ceba1832SHuw Davies }
521*ceba1832SHuw Davies 
522*ceba1832SHuw Davies /**
523*ceba1832SHuw Davies  * calipso_tlv_len - Returns the length of the TLV
524*ceba1832SHuw Davies  * @opt: the option header
525*ceba1832SHuw Davies  * @offset: offset of the TLV within the header
526*ceba1832SHuw Davies  *
527*ceba1832SHuw Davies  * Description:
528*ceba1832SHuw Davies  * Returns the length of the TLV option at offset @offset within
529*ceba1832SHuw Davies  * the option header @opt.  Checks that the entire TLV fits inside
530*ceba1832SHuw Davies  * the option header, returns a negative value if this is not the case.
531*ceba1832SHuw Davies  */
532*ceba1832SHuw Davies static int calipso_tlv_len(struct ipv6_opt_hdr *opt, unsigned int offset)
533*ceba1832SHuw Davies {
534*ceba1832SHuw Davies 	unsigned char *tlv = (unsigned char *)opt;
535*ceba1832SHuw Davies 	unsigned int opt_len = ipv6_optlen(opt), tlv_len;
536*ceba1832SHuw Davies 
537*ceba1832SHuw Davies 	if (offset < sizeof(*opt) || offset >= opt_len)
538*ceba1832SHuw Davies 		return -EINVAL;
539*ceba1832SHuw Davies 	if (tlv[offset] == IPV6_TLV_PAD1)
540*ceba1832SHuw Davies 		return 1;
541*ceba1832SHuw Davies 	if (offset + 1 >= opt_len)
542*ceba1832SHuw Davies 		return -EINVAL;
543*ceba1832SHuw Davies 	tlv_len = tlv[offset + 1] + 2;
544*ceba1832SHuw Davies 	if (offset + tlv_len > opt_len)
545*ceba1832SHuw Davies 		return -EINVAL;
546*ceba1832SHuw Davies 	return tlv_len;
547*ceba1832SHuw Davies }
548*ceba1832SHuw Davies 
549*ceba1832SHuw Davies /**
550*ceba1832SHuw Davies  * calipso_opt_find - Finds the CALIPSO option in an IPv6 hop options header
551*ceba1832SHuw Davies  * @hop: the hop options header
552*ceba1832SHuw Davies  * @start: on return holds the offset of any leading padding
553*ceba1832SHuw Davies  * @end: on return holds the offset of the first non-pad TLV after CALIPSO
554*ceba1832SHuw Davies  *
555*ceba1832SHuw Davies  * Description:
556*ceba1832SHuw Davies  * Finds the space occupied by a CALIPSO option (including any leading and
557*ceba1832SHuw Davies  * trailing padding).
558*ceba1832SHuw Davies  *
559*ceba1832SHuw Davies  * If a CALIPSO option exists set @start and @end to the
560*ceba1832SHuw Davies  * offsets within @hop of the start of padding before the first
561*ceba1832SHuw Davies  * CALIPSO option and the end of padding after the first CALIPSO
562*ceba1832SHuw Davies  * option.  In this case the function returns 0.
563*ceba1832SHuw Davies  *
564*ceba1832SHuw Davies  * In the absence of a CALIPSO option, @start and @end will be
565*ceba1832SHuw Davies  * set to the start and end of any trailing padding in the header.
566*ceba1832SHuw Davies  * This is useful when appending a new option, as the caller may want
567*ceba1832SHuw Davies  * to overwrite some of this padding.  In this case the function will
568*ceba1832SHuw Davies  * return -ENOENT.
569*ceba1832SHuw Davies  */
570*ceba1832SHuw Davies static int calipso_opt_find(struct ipv6_opt_hdr *hop, unsigned int *start,
571*ceba1832SHuw Davies 			    unsigned int *end)
572*ceba1832SHuw Davies {
573*ceba1832SHuw Davies 	int ret_val = -ENOENT, tlv_len;
574*ceba1832SHuw Davies 	unsigned int opt_len, offset, offset_s = 0, offset_e = 0;
575*ceba1832SHuw Davies 	unsigned char *opt = (unsigned char *)hop;
576*ceba1832SHuw Davies 
577*ceba1832SHuw Davies 	opt_len = ipv6_optlen(hop);
578*ceba1832SHuw Davies 	offset = sizeof(*hop);
579*ceba1832SHuw Davies 
580*ceba1832SHuw Davies 	while (offset < opt_len) {
581*ceba1832SHuw Davies 		tlv_len = calipso_tlv_len(hop, offset);
582*ceba1832SHuw Davies 		if (tlv_len < 0)
583*ceba1832SHuw Davies 			return tlv_len;
584*ceba1832SHuw Davies 
585*ceba1832SHuw Davies 		switch (opt[offset]) {
586*ceba1832SHuw Davies 		case IPV6_TLV_PAD1:
587*ceba1832SHuw Davies 		case IPV6_TLV_PADN:
588*ceba1832SHuw Davies 			if (offset_e)
589*ceba1832SHuw Davies 				offset_e = offset;
590*ceba1832SHuw Davies 			break;
591*ceba1832SHuw Davies 		case IPV6_TLV_CALIPSO:
592*ceba1832SHuw Davies 			ret_val = 0;
593*ceba1832SHuw Davies 			offset_e = offset;
594*ceba1832SHuw Davies 			break;
595*ceba1832SHuw Davies 		default:
596*ceba1832SHuw Davies 			if (offset_e == 0)
597*ceba1832SHuw Davies 				offset_s = offset;
598*ceba1832SHuw Davies 			else
599*ceba1832SHuw Davies 				goto out;
600*ceba1832SHuw Davies 		}
601*ceba1832SHuw Davies 		offset += tlv_len;
602*ceba1832SHuw Davies 	}
603*ceba1832SHuw Davies 
604*ceba1832SHuw Davies out:
605*ceba1832SHuw Davies 	if (offset_s)
606*ceba1832SHuw Davies 		*start = offset_s + calipso_tlv_len(hop, offset_s);
607*ceba1832SHuw Davies 	else
608*ceba1832SHuw Davies 		*start = sizeof(*hop);
609*ceba1832SHuw Davies 	if (offset_e)
610*ceba1832SHuw Davies 		*end = offset_e + calipso_tlv_len(hop, offset_e);
611*ceba1832SHuw Davies 	else
612*ceba1832SHuw Davies 		*end = opt_len;
613*ceba1832SHuw Davies 
614*ceba1832SHuw Davies 	return ret_val;
615*ceba1832SHuw Davies }
616*ceba1832SHuw Davies 
617*ceba1832SHuw Davies /**
618*ceba1832SHuw Davies  * calipso_opt_insert - Inserts a CALIPSO option into an IPv6 hop opt hdr
619*ceba1832SHuw Davies  * @hop: the original hop options header
620*ceba1832SHuw Davies  * @doi_def: the CALIPSO DOI to use
621*ceba1832SHuw Davies  * @secattr: the specific security attributes of the socket
622*ceba1832SHuw Davies  *
623*ceba1832SHuw Davies  * Description:
624*ceba1832SHuw Davies  * Creates a new hop options header based on @hop with a
625*ceba1832SHuw Davies  * CALIPSO option added to it.  If @hop already contains a CALIPSO
626*ceba1832SHuw Davies  * option this is overwritten, otherwise the new option is appended
627*ceba1832SHuw Davies  * after any existing options.  If @hop is NULL then the new header
628*ceba1832SHuw Davies  * will contain just the CALIPSO option and any needed padding.
629*ceba1832SHuw Davies  *
630*ceba1832SHuw Davies  */
631*ceba1832SHuw Davies static struct ipv6_opt_hdr *
632*ceba1832SHuw Davies calipso_opt_insert(struct ipv6_opt_hdr *hop,
633*ceba1832SHuw Davies 		   const struct calipso_doi *doi_def,
634*ceba1832SHuw Davies 		   const struct netlbl_lsm_secattr *secattr)
635*ceba1832SHuw Davies {
636*ceba1832SHuw Davies 	unsigned int start, end, buf_len, pad, hop_len;
637*ceba1832SHuw Davies 	struct ipv6_opt_hdr *new;
638*ceba1832SHuw Davies 	int ret_val;
639*ceba1832SHuw Davies 
640*ceba1832SHuw Davies 	if (hop) {
641*ceba1832SHuw Davies 		hop_len = ipv6_optlen(hop);
642*ceba1832SHuw Davies 		ret_val = calipso_opt_find(hop, &start, &end);
643*ceba1832SHuw Davies 		if (ret_val && ret_val != -ENOENT)
644*ceba1832SHuw Davies 			return ERR_PTR(ret_val);
645*ceba1832SHuw Davies 	} else {
646*ceba1832SHuw Davies 		hop_len = 0;
647*ceba1832SHuw Davies 		start = sizeof(*hop);
648*ceba1832SHuw Davies 		end = 0;
649*ceba1832SHuw Davies 	}
650*ceba1832SHuw Davies 
651*ceba1832SHuw Davies 	buf_len = hop_len + start - end + CALIPSO_OPT_LEN_MAX_WITH_PAD;
652*ceba1832SHuw Davies 	new = kzalloc(buf_len, GFP_ATOMIC);
653*ceba1832SHuw Davies 	if (!new)
654*ceba1832SHuw Davies 		return ERR_PTR(-ENOMEM);
655*ceba1832SHuw Davies 
656*ceba1832SHuw Davies 	if (start > sizeof(*hop))
657*ceba1832SHuw Davies 		memcpy(new, hop, start);
658*ceba1832SHuw Davies 	ret_val = calipso_genopt((unsigned char *)new, start, buf_len, doi_def,
659*ceba1832SHuw Davies 				 secattr);
660*ceba1832SHuw Davies 	if (ret_val < 0)
661*ceba1832SHuw Davies 		return ERR_PTR(ret_val);
662*ceba1832SHuw Davies 
663*ceba1832SHuw Davies 	buf_len = start + ret_val;
664*ceba1832SHuw Davies 	/* At this point buf_len aligns to 4n, so (buf_len & 4) pads to 8n */
665*ceba1832SHuw Davies 	pad = ((buf_len & 4) + (end & 7)) & 7;
666*ceba1832SHuw Davies 	calipso_pad_write((unsigned char *)new, buf_len, pad);
667*ceba1832SHuw Davies 	buf_len += pad;
668*ceba1832SHuw Davies 
669*ceba1832SHuw Davies 	if (end != hop_len) {
670*ceba1832SHuw Davies 		memcpy((char *)new + buf_len, (char *)hop + end, hop_len - end);
671*ceba1832SHuw Davies 		buf_len += hop_len - end;
672*ceba1832SHuw Davies 	}
673*ceba1832SHuw Davies 	new->nexthdr = 0;
674*ceba1832SHuw Davies 	new->hdrlen = buf_len / 8 - 1;
675*ceba1832SHuw Davies 
676*ceba1832SHuw Davies 	return new;
677*ceba1832SHuw Davies }
678*ceba1832SHuw Davies 
679*ceba1832SHuw Davies /**
680*ceba1832SHuw Davies  * calipso_opt_del - Removes the CALIPSO option from an option header
681*ceba1832SHuw Davies  * @hop: the original header
682*ceba1832SHuw Davies  * @new: the new header
683*ceba1832SHuw Davies  *
684*ceba1832SHuw Davies  * Description:
685*ceba1832SHuw Davies  * Creates a new header based on @hop without any CALIPSO option.  If @hop
686*ceba1832SHuw Davies  * doesn't contain a CALIPSO option it returns -ENOENT.  If @hop contains
687*ceba1832SHuw Davies  * no other non-padding options, it returns zero with @new set to NULL.
688*ceba1832SHuw Davies  * Otherwise it returns zero, creates a new header without the CALIPSO
689*ceba1832SHuw Davies  * option (and removing as much padding as possible) and returns with
690*ceba1832SHuw Davies  * @new set to that header.
691*ceba1832SHuw Davies  *
692*ceba1832SHuw Davies  */
693*ceba1832SHuw Davies static int calipso_opt_del(struct ipv6_opt_hdr *hop,
694*ceba1832SHuw Davies 			   struct ipv6_opt_hdr **new)
695*ceba1832SHuw Davies {
696*ceba1832SHuw Davies 	int ret_val;
697*ceba1832SHuw Davies 	unsigned int start, end, delta, pad, hop_len;
698*ceba1832SHuw Davies 
699*ceba1832SHuw Davies 	ret_val = calipso_opt_find(hop, &start, &end);
700*ceba1832SHuw Davies 	if (ret_val)
701*ceba1832SHuw Davies 		return ret_val;
702*ceba1832SHuw Davies 
703*ceba1832SHuw Davies 	hop_len = ipv6_optlen(hop);
704*ceba1832SHuw Davies 	if (start == sizeof(*hop) && end == hop_len) {
705*ceba1832SHuw Davies 		/* There's no other option in the header so return NULL */
706*ceba1832SHuw Davies 		*new = NULL;
707*ceba1832SHuw Davies 		return 0;
708*ceba1832SHuw Davies 	}
709*ceba1832SHuw Davies 
710*ceba1832SHuw Davies 	delta = (end - start) & ~7;
711*ceba1832SHuw Davies 	*new = kzalloc(hop_len - delta, GFP_ATOMIC);
712*ceba1832SHuw Davies 	if (!*new)
713*ceba1832SHuw Davies 		return -ENOMEM;
714*ceba1832SHuw Davies 
715*ceba1832SHuw Davies 	memcpy(*new, hop, start);
716*ceba1832SHuw Davies 	(*new)->hdrlen -= delta / 8;
717*ceba1832SHuw Davies 	pad = (end - start) & 7;
718*ceba1832SHuw Davies 	calipso_pad_write((unsigned char *)*new, start, pad);
719*ceba1832SHuw Davies 	if (end != hop_len)
720*ceba1832SHuw Davies 		memcpy((char *)*new + start + pad, (char *)hop + end,
721*ceba1832SHuw Davies 		       hop_len - end);
722*ceba1832SHuw Davies 
723*ceba1832SHuw Davies 	return 0;
724*ceba1832SHuw Davies }
725*ceba1832SHuw Davies 
726*ceba1832SHuw Davies /**
727*ceba1832SHuw Davies  * calipso_opt_getattr - Get the security attributes from a memory block
728*ceba1832SHuw Davies  * @calipso: the CALIPSO option
729*ceba1832SHuw Davies  * @secattr: the security attributes
730*ceba1832SHuw Davies  *
731*ceba1832SHuw Davies  * Description:
732*ceba1832SHuw Davies  * Inspect @calipso and return the security attributes in @secattr.
733*ceba1832SHuw Davies  * Returns zero on success and negative values on failure.
734*ceba1832SHuw Davies  *
735*ceba1832SHuw Davies  */
736*ceba1832SHuw Davies static int calipso_opt_getattr(const unsigned char *calipso,
737*ceba1832SHuw Davies 			       struct netlbl_lsm_secattr *secattr)
738*ceba1832SHuw Davies {
739*ceba1832SHuw Davies 	int ret_val = -ENOMSG;
740*ceba1832SHuw Davies 	u32 doi, len = calipso[1], cat_len = calipso[6] * 4;
741*ceba1832SHuw Davies 	struct calipso_doi *doi_def;
742*ceba1832SHuw Davies 
743*ceba1832SHuw Davies 	if (cat_len + 8 > len)
744*ceba1832SHuw Davies 		return -EINVAL;
745*ceba1832SHuw Davies 
746*ceba1832SHuw Davies 	doi = get_unaligned_be32(calipso + 2);
747*ceba1832SHuw Davies 	rcu_read_lock();
748*ceba1832SHuw Davies 	doi_def = calipso_doi_search(doi);
749*ceba1832SHuw Davies 	if (!doi_def)
750*ceba1832SHuw Davies 		goto getattr_return;
751*ceba1832SHuw Davies 
752*ceba1832SHuw Davies 	secattr->attr.mls.lvl = calipso[7];
753*ceba1832SHuw Davies 	secattr->flags |= NETLBL_SECATTR_MLS_LVL;
754*ceba1832SHuw Davies 
755*ceba1832SHuw Davies 	if (cat_len) {
756*ceba1832SHuw Davies 		ret_val = calipso_map_cat_ntoh(doi_def,
757*ceba1832SHuw Davies 					       calipso + 10,
758*ceba1832SHuw Davies 					       cat_len,
759*ceba1832SHuw Davies 					       secattr);
760*ceba1832SHuw Davies 		if (ret_val != 0) {
761*ceba1832SHuw Davies 			netlbl_catmap_free(secattr->attr.mls.cat);
762*ceba1832SHuw Davies 			goto getattr_return;
763*ceba1832SHuw Davies 		}
764*ceba1832SHuw Davies 
765*ceba1832SHuw Davies 		secattr->flags |= NETLBL_SECATTR_MLS_CAT;
766*ceba1832SHuw Davies 	}
767*ceba1832SHuw Davies 
768*ceba1832SHuw Davies 	secattr->type = NETLBL_NLTYPE_CALIPSO;
769*ceba1832SHuw Davies 
770*ceba1832SHuw Davies getattr_return:
771*ceba1832SHuw Davies 	rcu_read_unlock();
772*ceba1832SHuw Davies 	return ret_val;
773*ceba1832SHuw Davies }
774*ceba1832SHuw Davies 
775*ceba1832SHuw Davies /* sock functions.
776*ceba1832SHuw Davies  */
777*ceba1832SHuw Davies 
778*ceba1832SHuw Davies /**
779*ceba1832SHuw Davies  * calipso_sock_getattr - Get the security attributes from a sock
780*ceba1832SHuw Davies  * @sk: the sock
781*ceba1832SHuw Davies  * @secattr: the security attributes
782*ceba1832SHuw Davies  *
783*ceba1832SHuw Davies  * Description:
784*ceba1832SHuw Davies  * Query @sk to see if there is a CALIPSO option attached to the sock and if
785*ceba1832SHuw Davies  * there is return the CALIPSO security attributes in @secattr.  This function
786*ceba1832SHuw Davies  * requires that @sk be locked, or privately held, but it does not do any
787*ceba1832SHuw Davies  * locking itself.  Returns zero on success and negative values on failure.
788*ceba1832SHuw Davies  *
789*ceba1832SHuw Davies  */
790*ceba1832SHuw Davies static int calipso_sock_getattr(struct sock *sk,
791*ceba1832SHuw Davies 				struct netlbl_lsm_secattr *secattr)
792*ceba1832SHuw Davies {
793*ceba1832SHuw Davies 	struct ipv6_opt_hdr *hop;
794*ceba1832SHuw Davies 	int opt_len, len, ret_val = -ENOMSG, offset;
795*ceba1832SHuw Davies 	unsigned char *opt;
796*ceba1832SHuw Davies 	struct ipv6_txoptions *txopts = txopt_get(inet6_sk(sk));
797*ceba1832SHuw Davies 
798*ceba1832SHuw Davies 	if (!txopts || !txopts->hopopt)
799*ceba1832SHuw Davies 		goto done;
800*ceba1832SHuw Davies 
801*ceba1832SHuw Davies 	hop = txopts->hopopt;
802*ceba1832SHuw Davies 	opt = (unsigned char *)hop;
803*ceba1832SHuw Davies 	opt_len = ipv6_optlen(hop);
804*ceba1832SHuw Davies 	offset = sizeof(*hop);
805*ceba1832SHuw Davies 	while (offset < opt_len) {
806*ceba1832SHuw Davies 		len = calipso_tlv_len(hop, offset);
807*ceba1832SHuw Davies 		if (len < 0) {
808*ceba1832SHuw Davies 			ret_val = len;
809*ceba1832SHuw Davies 			goto done;
810*ceba1832SHuw Davies 		}
811*ceba1832SHuw Davies 		switch (opt[offset]) {
812*ceba1832SHuw Davies 		case IPV6_TLV_CALIPSO:
813*ceba1832SHuw Davies 			if (len < CALIPSO_HDR_LEN)
814*ceba1832SHuw Davies 				ret_val = -EINVAL;
815*ceba1832SHuw Davies 			else
816*ceba1832SHuw Davies 				ret_val = calipso_opt_getattr(&opt[offset],
817*ceba1832SHuw Davies 							      secattr);
818*ceba1832SHuw Davies 			goto done;
819*ceba1832SHuw Davies 		default:
820*ceba1832SHuw Davies 			offset += len;
821*ceba1832SHuw Davies 			break;
822*ceba1832SHuw Davies 		}
823*ceba1832SHuw Davies 	}
824*ceba1832SHuw Davies done:
825*ceba1832SHuw Davies 	txopt_put(txopts);
826*ceba1832SHuw Davies 	return ret_val;
827*ceba1832SHuw Davies }
828*ceba1832SHuw Davies 
829*ceba1832SHuw Davies /**
830*ceba1832SHuw Davies  * calipso_sock_setattr - Add a CALIPSO option to a socket
831*ceba1832SHuw Davies  * @sk: the socket
832*ceba1832SHuw Davies  * @doi_def: the CALIPSO DOI to use
833*ceba1832SHuw Davies  * @secattr: the specific security attributes of the socket
834*ceba1832SHuw Davies  *
835*ceba1832SHuw Davies  * Description:
836*ceba1832SHuw Davies  * Set the CALIPSO option on the given socket using the DOI definition and
837*ceba1832SHuw Davies  * security attributes passed to the function.  This function requires
838*ceba1832SHuw Davies  * exclusive access to @sk, which means it either needs to be in the
839*ceba1832SHuw Davies  * process of being created or locked.  Returns zero on success and negative
840*ceba1832SHuw Davies  * values on failure.
841*ceba1832SHuw Davies  *
842*ceba1832SHuw Davies  */
843*ceba1832SHuw Davies static int calipso_sock_setattr(struct sock *sk,
844*ceba1832SHuw Davies 				const struct calipso_doi *doi_def,
845*ceba1832SHuw Davies 				const struct netlbl_lsm_secattr *secattr)
846*ceba1832SHuw Davies {
847*ceba1832SHuw Davies 	int ret_val;
848*ceba1832SHuw Davies 	struct ipv6_opt_hdr *old, *new;
849*ceba1832SHuw Davies 	struct ipv6_txoptions *txopts = txopt_get(inet6_sk(sk));
850*ceba1832SHuw Davies 
851*ceba1832SHuw Davies 	old = NULL;
852*ceba1832SHuw Davies 	if (txopts)
853*ceba1832SHuw Davies 		old = txopts->hopopt;
854*ceba1832SHuw Davies 
855*ceba1832SHuw Davies 	new = calipso_opt_insert(old, doi_def, secattr);
856*ceba1832SHuw Davies 	txopt_put(txopts);
857*ceba1832SHuw Davies 	if (IS_ERR(new))
858*ceba1832SHuw Davies 		return PTR_ERR(new);
859*ceba1832SHuw Davies 
860*ceba1832SHuw Davies 	ret_val = calipso_opt_update(sk, new);
861*ceba1832SHuw Davies 
862*ceba1832SHuw Davies 	kfree(new);
863*ceba1832SHuw Davies 	return ret_val;
864*ceba1832SHuw Davies }
865*ceba1832SHuw Davies 
866*ceba1832SHuw Davies /**
867*ceba1832SHuw Davies  * calipso_sock_delattr - Delete the CALIPSO option from a socket
868*ceba1832SHuw Davies  * @sk: the socket
869*ceba1832SHuw Davies  *
870*ceba1832SHuw Davies  * Description:
871*ceba1832SHuw Davies  * Removes the CALIPSO option from a socket, if present.
872*ceba1832SHuw Davies  *
873*ceba1832SHuw Davies  */
874*ceba1832SHuw Davies static void calipso_sock_delattr(struct sock *sk)
875*ceba1832SHuw Davies {
876*ceba1832SHuw Davies 	struct ipv6_opt_hdr *new_hop;
877*ceba1832SHuw Davies 	struct ipv6_txoptions *txopts = txopt_get(inet6_sk(sk));
878*ceba1832SHuw Davies 
879*ceba1832SHuw Davies 	if (!txopts || !txopts->hopopt)
880*ceba1832SHuw Davies 		goto done;
881*ceba1832SHuw Davies 
882*ceba1832SHuw Davies 	if (calipso_opt_del(txopts->hopopt, &new_hop))
883*ceba1832SHuw Davies 		goto done;
884*ceba1832SHuw Davies 
885*ceba1832SHuw Davies 	calipso_opt_update(sk, new_hop);
886*ceba1832SHuw Davies 	kfree(new_hop);
887*ceba1832SHuw Davies 
888*ceba1832SHuw Davies done:
889*ceba1832SHuw Davies 	txopt_put(txopts);
890*ceba1832SHuw Davies }
891*ceba1832SHuw Davies 
892cb72d382SHuw Davies static const struct netlbl_calipso_ops ops = {
893cb72d382SHuw Davies 	.doi_add          = calipso_doi_add,
894cb72d382SHuw Davies 	.doi_free         = calipso_doi_free,
895d7cce015SHuw Davies 	.doi_remove       = calipso_doi_remove,
896a5e34490SHuw Davies 	.doi_getdef       = calipso_doi_getdef,
897a5e34490SHuw Davies 	.doi_putdef       = calipso_doi_putdef,
898e1ce69dfSHuw Davies 	.doi_walk         = calipso_doi_walk,
899*ceba1832SHuw Davies 	.sock_getattr     = calipso_sock_getattr,
900*ceba1832SHuw Davies 	.sock_setattr     = calipso_sock_setattr,
901*ceba1832SHuw Davies 	.sock_delattr     = calipso_sock_delattr,
902cb72d382SHuw Davies };
903cb72d382SHuw Davies 
904cb72d382SHuw Davies /**
905cb72d382SHuw Davies  * calipso_init - Initialize the CALIPSO module
906cb72d382SHuw Davies  *
907cb72d382SHuw Davies  * Description:
908cb72d382SHuw Davies  * Initialize the CALIPSO module and prepare it for use.  Returns zero on
909cb72d382SHuw Davies  * success and negative values on failure.
910cb72d382SHuw Davies  *
911cb72d382SHuw Davies  */
912cb72d382SHuw Davies int __init calipso_init(void)
913cb72d382SHuw Davies {
914cb72d382SHuw Davies 	netlbl_calipso_ops_register(&ops);
915cb72d382SHuw Davies 	return 0;
916cb72d382SHuw Davies }
917cb72d382SHuw Davies 
918cb72d382SHuw Davies void calipso_exit(void)
919cb72d382SHuw Davies {
920cb72d382SHuw Davies 	netlbl_calipso_ops_register(NULL);
921cb72d382SHuw Davies }
922