1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * NetLabel Management Support |
4 | * |
5 | * This file defines the management functions for the NetLabel system. The |
6 | * NetLabel system manages static and dynamic label mappings for network |
7 | * protocols such as CIPSO and RIPSO. |
8 | * |
9 | * Author: Paul Moore <paul@paul-moore.com> |
10 | */ |
11 | |
12 | /* |
13 | * (c) Copyright Hewlett-Packard Development Company, L.P., 2006, 2008 |
14 | */ |
15 | |
16 | #include <linux/types.h> |
17 | #include <linux/socket.h> |
18 | #include <linux/string.h> |
19 | #include <linux/skbuff.h> |
20 | #include <linux/in.h> |
21 | #include <linux/in6.h> |
22 | #include <linux/slab.h> |
23 | #include <net/sock.h> |
24 | #include <net/netlink.h> |
25 | #include <net/genetlink.h> |
26 | #include <net/ip.h> |
27 | #include <net/ipv6.h> |
28 | #include <net/netlabel.h> |
29 | #include <net/cipso_ipv4.h> |
30 | #include <net/calipso.h> |
31 | #include <linux/atomic.h> |
32 | |
33 | #include "netlabel_calipso.h" |
34 | #include "netlabel_domainhash.h" |
35 | #include "netlabel_user.h" |
36 | #include "netlabel_mgmt.h" |
37 | |
38 | /* NetLabel configured protocol counter */ |
39 | atomic_t netlabel_mgmt_protocount = ATOMIC_INIT(0); |
40 | |
41 | /* Argument struct for netlbl_domhsh_walk() */ |
42 | struct netlbl_domhsh_walk_arg { |
43 | struct netlink_callback *nl_cb; |
44 | struct sk_buff *skb; |
45 | u32 seq; |
46 | }; |
47 | |
48 | /* NetLabel Generic NETLINK CIPSOv4 family */ |
49 | static struct genl_family netlbl_mgmt_gnl_family; |
50 | |
51 | /* NetLabel Netlink attribute policy */ |
52 | static const struct nla_policy netlbl_mgmt_genl_policy[NLBL_MGMT_A_MAX + 1] = { |
53 | [NLBL_MGMT_A_DOMAIN] = { .type = NLA_NUL_STRING }, |
54 | [NLBL_MGMT_A_PROTOCOL] = { .type = NLA_U32 }, |
55 | [NLBL_MGMT_A_VERSION] = { .type = NLA_U32 }, |
56 | [NLBL_MGMT_A_CV4DOI] = { .type = NLA_U32 }, |
57 | [NLBL_MGMT_A_FAMILY] = { .type = NLA_U16 }, |
58 | [NLBL_MGMT_A_CLPDOI] = { .type = NLA_U32 }, |
59 | }; |
60 | |
61 | /* |
62 | * Helper Functions |
63 | */ |
64 | |
65 | /** |
66 | * netlbl_mgmt_add_common - Handle an ADD message |
67 | * @info: the Generic NETLINK info block |
68 | * @audit_info: NetLabel audit information |
69 | * |
70 | * Description: |
71 | * Helper function for the ADD and ADDDEF messages to add the domain mappings |
72 | * from the message to the hash table. See netlabel.h for a description of the |
73 | * message format. Returns zero on success, negative values on failure. |
74 | * |
75 | */ |
76 | static int netlbl_mgmt_add_common(struct genl_info *info, |
77 | struct netlbl_audit *audit_info) |
78 | { |
79 | void *pmap = NULL; |
80 | int ret_val = -EINVAL; |
81 | struct netlbl_domaddr_map *addrmap = NULL; |
82 | struct cipso_v4_doi *cipsov4 = NULL; |
83 | #if IS_ENABLED(CONFIG_IPV6) |
84 | struct calipso_doi *calipso = NULL; |
85 | #endif |
86 | u32 tmp_val; |
87 | struct netlbl_dom_map *entry = kzalloc(size: sizeof(*entry), GFP_KERNEL); |
88 | |
89 | if (!entry) |
90 | return -ENOMEM; |
91 | entry->def.type = nla_get_u32(nla: info->attrs[NLBL_MGMT_A_PROTOCOL]); |
92 | if (info->attrs[NLBL_MGMT_A_DOMAIN]) { |
93 | size_t tmp_size = nla_len(nla: info->attrs[NLBL_MGMT_A_DOMAIN]); |
94 | entry->domain = kmalloc(size: tmp_size, GFP_KERNEL); |
95 | if (entry->domain == NULL) { |
96 | ret_val = -ENOMEM; |
97 | goto add_free_entry; |
98 | } |
99 | nla_strscpy(dst: entry->domain, |
100 | nla: info->attrs[NLBL_MGMT_A_DOMAIN], dstsize: tmp_size); |
101 | } |
102 | |
103 | /* NOTE: internally we allow/use a entry->def.type value of |
104 | * NETLBL_NLTYPE_ADDRSELECT but we don't currently allow users |
105 | * to pass that as a protocol value because we need to know the |
106 | * "real" protocol */ |
107 | |
108 | switch (entry->def.type) { |
109 | case NETLBL_NLTYPE_UNLABELED: |
110 | if (info->attrs[NLBL_MGMT_A_FAMILY]) |
111 | entry->family = |
112 | nla_get_u16(nla: info->attrs[NLBL_MGMT_A_FAMILY]); |
113 | else |
114 | entry->family = AF_UNSPEC; |
115 | break; |
116 | case NETLBL_NLTYPE_CIPSOV4: |
117 | if (!info->attrs[NLBL_MGMT_A_CV4DOI]) |
118 | goto add_free_domain; |
119 | |
120 | tmp_val = nla_get_u32(nla: info->attrs[NLBL_MGMT_A_CV4DOI]); |
121 | cipsov4 = cipso_v4_doi_getdef(doi: tmp_val); |
122 | if (cipsov4 == NULL) |
123 | goto add_free_domain; |
124 | entry->family = AF_INET; |
125 | entry->def.cipso = cipsov4; |
126 | break; |
127 | #if IS_ENABLED(CONFIG_IPV6) |
128 | case NETLBL_NLTYPE_CALIPSO: |
129 | if (!info->attrs[NLBL_MGMT_A_CLPDOI]) |
130 | goto add_free_domain; |
131 | |
132 | tmp_val = nla_get_u32(nla: info->attrs[NLBL_MGMT_A_CLPDOI]); |
133 | calipso = calipso_doi_getdef(doi: tmp_val); |
134 | if (calipso == NULL) |
135 | goto add_free_domain; |
136 | entry->family = AF_INET6; |
137 | entry->def.calipso = calipso; |
138 | break; |
139 | #endif /* IPv6 */ |
140 | default: |
141 | goto add_free_domain; |
142 | } |
143 | |
144 | if ((entry->family == AF_INET && info->attrs[NLBL_MGMT_A_IPV6ADDR]) || |
145 | (entry->family == AF_INET6 && info->attrs[NLBL_MGMT_A_IPV4ADDR])) |
146 | goto add_doi_put_def; |
147 | |
148 | if (info->attrs[NLBL_MGMT_A_IPV4ADDR]) { |
149 | struct in_addr *addr; |
150 | struct in_addr *mask; |
151 | struct netlbl_domaddr4_map *map; |
152 | |
153 | addrmap = kzalloc(size: sizeof(*addrmap), GFP_KERNEL); |
154 | if (addrmap == NULL) { |
155 | ret_val = -ENOMEM; |
156 | goto add_doi_put_def; |
157 | } |
158 | INIT_LIST_HEAD(list: &addrmap->list4); |
159 | INIT_LIST_HEAD(list: &addrmap->list6); |
160 | |
161 | if (nla_len(nla: info->attrs[NLBL_MGMT_A_IPV4ADDR]) != |
162 | sizeof(struct in_addr)) { |
163 | ret_val = -EINVAL; |
164 | goto add_free_addrmap; |
165 | } |
166 | if (nla_len(nla: info->attrs[NLBL_MGMT_A_IPV4MASK]) != |
167 | sizeof(struct in_addr)) { |
168 | ret_val = -EINVAL; |
169 | goto add_free_addrmap; |
170 | } |
171 | addr = nla_data(nla: info->attrs[NLBL_MGMT_A_IPV4ADDR]); |
172 | mask = nla_data(nla: info->attrs[NLBL_MGMT_A_IPV4MASK]); |
173 | |
174 | map = kzalloc(size: sizeof(*map), GFP_KERNEL); |
175 | if (map == NULL) { |
176 | ret_val = -ENOMEM; |
177 | goto add_free_addrmap; |
178 | } |
179 | pmap = map; |
180 | map->list.addr = addr->s_addr & mask->s_addr; |
181 | map->list.mask = mask->s_addr; |
182 | map->list.valid = 1; |
183 | map->def.type = entry->def.type; |
184 | if (cipsov4) |
185 | map->def.cipso = cipsov4; |
186 | |
187 | ret_val = netlbl_af4list_add(entry: &map->list, head: &addrmap->list4); |
188 | if (ret_val != 0) |
189 | goto add_free_map; |
190 | |
191 | entry->family = AF_INET; |
192 | entry->def.type = NETLBL_NLTYPE_ADDRSELECT; |
193 | entry->def.addrsel = addrmap; |
194 | #if IS_ENABLED(CONFIG_IPV6) |
195 | } else if (info->attrs[NLBL_MGMT_A_IPV6ADDR]) { |
196 | struct in6_addr *addr; |
197 | struct in6_addr *mask; |
198 | struct netlbl_domaddr6_map *map; |
199 | |
200 | addrmap = kzalloc(size: sizeof(*addrmap), GFP_KERNEL); |
201 | if (addrmap == NULL) { |
202 | ret_val = -ENOMEM; |
203 | goto add_doi_put_def; |
204 | } |
205 | INIT_LIST_HEAD(list: &addrmap->list4); |
206 | INIT_LIST_HEAD(list: &addrmap->list6); |
207 | |
208 | if (nla_len(nla: info->attrs[NLBL_MGMT_A_IPV6ADDR]) != |
209 | sizeof(struct in6_addr)) { |
210 | ret_val = -EINVAL; |
211 | goto add_free_addrmap; |
212 | } |
213 | if (nla_len(nla: info->attrs[NLBL_MGMT_A_IPV6MASK]) != |
214 | sizeof(struct in6_addr)) { |
215 | ret_val = -EINVAL; |
216 | goto add_free_addrmap; |
217 | } |
218 | addr = nla_data(nla: info->attrs[NLBL_MGMT_A_IPV6ADDR]); |
219 | mask = nla_data(nla: info->attrs[NLBL_MGMT_A_IPV6MASK]); |
220 | |
221 | map = kzalloc(size: sizeof(*map), GFP_KERNEL); |
222 | if (map == NULL) { |
223 | ret_val = -ENOMEM; |
224 | goto add_free_addrmap; |
225 | } |
226 | pmap = map; |
227 | map->list.addr = *addr; |
228 | map->list.addr.s6_addr32[0] &= mask->s6_addr32[0]; |
229 | map->list.addr.s6_addr32[1] &= mask->s6_addr32[1]; |
230 | map->list.addr.s6_addr32[2] &= mask->s6_addr32[2]; |
231 | map->list.addr.s6_addr32[3] &= mask->s6_addr32[3]; |
232 | map->list.mask = *mask; |
233 | map->list.valid = 1; |
234 | map->def.type = entry->def.type; |
235 | if (calipso) |
236 | map->def.calipso = calipso; |
237 | |
238 | ret_val = netlbl_af6list_add(entry: &map->list, head: &addrmap->list6); |
239 | if (ret_val != 0) |
240 | goto add_free_map; |
241 | |
242 | entry->family = AF_INET6; |
243 | entry->def.type = NETLBL_NLTYPE_ADDRSELECT; |
244 | entry->def.addrsel = addrmap; |
245 | #endif /* IPv6 */ |
246 | } |
247 | |
248 | ret_val = netlbl_domhsh_add(entry, audit_info); |
249 | if (ret_val != 0) |
250 | goto add_free_map; |
251 | |
252 | return 0; |
253 | |
254 | add_free_map: |
255 | kfree(objp: pmap); |
256 | add_free_addrmap: |
257 | kfree(objp: addrmap); |
258 | add_doi_put_def: |
259 | cipso_v4_doi_putdef(doi_def: cipsov4); |
260 | #if IS_ENABLED(CONFIG_IPV6) |
261 | calipso_doi_putdef(doi_def: calipso); |
262 | #endif |
263 | add_free_domain: |
264 | kfree(objp: entry->domain); |
265 | add_free_entry: |
266 | kfree(objp: entry); |
267 | return ret_val; |
268 | } |
269 | |
270 | /** |
271 | * netlbl_mgmt_listentry - List a NetLabel/LSM domain map entry |
272 | * @skb: the NETLINK buffer |
273 | * @entry: the map entry |
274 | * |
275 | * Description: |
276 | * This function is a helper function used by the LISTALL and LISTDEF command |
277 | * handlers. The caller is responsible for ensuring that the RCU read lock |
278 | * is held. Returns zero on success, negative values on failure. |
279 | * |
280 | */ |
281 | static int netlbl_mgmt_listentry(struct sk_buff *skb, |
282 | struct netlbl_dom_map *entry) |
283 | { |
284 | int ret_val = 0; |
285 | struct nlattr *nla_a; |
286 | struct nlattr *nla_b; |
287 | struct netlbl_af4list *iter4; |
288 | #if IS_ENABLED(CONFIG_IPV6) |
289 | struct netlbl_af6list *iter6; |
290 | #endif |
291 | |
292 | if (entry->domain != NULL) { |
293 | ret_val = nla_put_string(skb, |
294 | attrtype: NLBL_MGMT_A_DOMAIN, str: entry->domain); |
295 | if (ret_val != 0) |
296 | return ret_val; |
297 | } |
298 | |
299 | ret_val = nla_put_u16(skb, attrtype: NLBL_MGMT_A_FAMILY, value: entry->family); |
300 | if (ret_val != 0) |
301 | return ret_val; |
302 | |
303 | switch (entry->def.type) { |
304 | case NETLBL_NLTYPE_ADDRSELECT: |
305 | nla_a = nla_nest_start_noflag(skb, attrtype: NLBL_MGMT_A_SELECTORLIST); |
306 | if (nla_a == NULL) |
307 | return -ENOMEM; |
308 | |
309 | netlbl_af4list_foreach_rcu(iter4, &entry->def.addrsel->list4) { |
310 | struct netlbl_domaddr4_map *map4; |
311 | struct in_addr addr_struct; |
312 | |
313 | nla_b = nla_nest_start_noflag(skb, |
314 | attrtype: NLBL_MGMT_A_ADDRSELECTOR); |
315 | if (nla_b == NULL) |
316 | return -ENOMEM; |
317 | |
318 | addr_struct.s_addr = iter4->addr; |
319 | ret_val = nla_put_in_addr(skb, attrtype: NLBL_MGMT_A_IPV4ADDR, |
320 | addr: addr_struct.s_addr); |
321 | if (ret_val != 0) |
322 | return ret_val; |
323 | addr_struct.s_addr = iter4->mask; |
324 | ret_val = nla_put_in_addr(skb, attrtype: NLBL_MGMT_A_IPV4MASK, |
325 | addr: addr_struct.s_addr); |
326 | if (ret_val != 0) |
327 | return ret_val; |
328 | map4 = netlbl_domhsh_addr4_entry(iter4); |
329 | ret_val = nla_put_u32(skb, attrtype: NLBL_MGMT_A_PROTOCOL, |
330 | value: map4->def.type); |
331 | if (ret_val != 0) |
332 | return ret_val; |
333 | switch (map4->def.type) { |
334 | case NETLBL_NLTYPE_CIPSOV4: |
335 | ret_val = nla_put_u32(skb, attrtype: NLBL_MGMT_A_CV4DOI, |
336 | value: map4->def.cipso->doi); |
337 | if (ret_val != 0) |
338 | return ret_val; |
339 | break; |
340 | } |
341 | |
342 | nla_nest_end(skb, start: nla_b); |
343 | } |
344 | #if IS_ENABLED(CONFIG_IPV6) |
345 | netlbl_af6list_foreach_rcu(iter6, &entry->def.addrsel->list6) { |
346 | struct netlbl_domaddr6_map *map6; |
347 | |
348 | nla_b = nla_nest_start_noflag(skb, |
349 | attrtype: NLBL_MGMT_A_ADDRSELECTOR); |
350 | if (nla_b == NULL) |
351 | return -ENOMEM; |
352 | |
353 | ret_val = nla_put_in6_addr(skb, attrtype: NLBL_MGMT_A_IPV6ADDR, |
354 | addr: &iter6->addr); |
355 | if (ret_val != 0) |
356 | return ret_val; |
357 | ret_val = nla_put_in6_addr(skb, attrtype: NLBL_MGMT_A_IPV6MASK, |
358 | addr: &iter6->mask); |
359 | if (ret_val != 0) |
360 | return ret_val; |
361 | map6 = netlbl_domhsh_addr6_entry(iter6); |
362 | ret_val = nla_put_u32(skb, attrtype: NLBL_MGMT_A_PROTOCOL, |
363 | value: map6->def.type); |
364 | if (ret_val != 0) |
365 | return ret_val; |
366 | |
367 | switch (map6->def.type) { |
368 | case NETLBL_NLTYPE_CALIPSO: |
369 | ret_val = nla_put_u32(skb, attrtype: NLBL_MGMT_A_CLPDOI, |
370 | value: map6->def.calipso->doi); |
371 | if (ret_val != 0) |
372 | return ret_val; |
373 | break; |
374 | } |
375 | |
376 | nla_nest_end(skb, start: nla_b); |
377 | } |
378 | #endif /* IPv6 */ |
379 | |
380 | nla_nest_end(skb, start: nla_a); |
381 | break; |
382 | case NETLBL_NLTYPE_UNLABELED: |
383 | ret_val = nla_put_u32(skb, attrtype: NLBL_MGMT_A_PROTOCOL, |
384 | value: entry->def.type); |
385 | break; |
386 | case NETLBL_NLTYPE_CIPSOV4: |
387 | ret_val = nla_put_u32(skb, attrtype: NLBL_MGMT_A_PROTOCOL, |
388 | value: entry->def.type); |
389 | if (ret_val != 0) |
390 | return ret_val; |
391 | ret_val = nla_put_u32(skb, attrtype: NLBL_MGMT_A_CV4DOI, |
392 | value: entry->def.cipso->doi); |
393 | break; |
394 | case NETLBL_NLTYPE_CALIPSO: |
395 | ret_val = nla_put_u32(skb, attrtype: NLBL_MGMT_A_PROTOCOL, |
396 | value: entry->def.type); |
397 | if (ret_val != 0) |
398 | return ret_val; |
399 | ret_val = nla_put_u32(skb, attrtype: NLBL_MGMT_A_CLPDOI, |
400 | value: entry->def.calipso->doi); |
401 | break; |
402 | } |
403 | |
404 | return ret_val; |
405 | } |
406 | |
407 | /* |
408 | * NetLabel Command Handlers |
409 | */ |
410 | |
411 | /** |
412 | * netlbl_mgmt_add - Handle an ADD message |
413 | * @skb: the NETLINK buffer |
414 | * @info: the Generic NETLINK info block |
415 | * |
416 | * Description: |
417 | * Process a user generated ADD message and add the domains from the message |
418 | * to the hash table. See netlabel.h for a description of the message format. |
419 | * Returns zero on success, negative values on failure. |
420 | * |
421 | */ |
422 | static int netlbl_mgmt_add(struct sk_buff *skb, struct genl_info *info) |
423 | { |
424 | struct netlbl_audit audit_info; |
425 | |
426 | if ((!info->attrs[NLBL_MGMT_A_DOMAIN]) || |
427 | (!info->attrs[NLBL_MGMT_A_PROTOCOL]) || |
428 | (info->attrs[NLBL_MGMT_A_IPV4ADDR] && |
429 | info->attrs[NLBL_MGMT_A_IPV6ADDR]) || |
430 | (info->attrs[NLBL_MGMT_A_IPV4MASK] && |
431 | info->attrs[NLBL_MGMT_A_IPV6MASK]) || |
432 | ((info->attrs[NLBL_MGMT_A_IPV4ADDR] != NULL) ^ |
433 | (info->attrs[NLBL_MGMT_A_IPV4MASK] != NULL)) || |
434 | ((info->attrs[NLBL_MGMT_A_IPV6ADDR] != NULL) ^ |
435 | (info->attrs[NLBL_MGMT_A_IPV6MASK] != NULL))) |
436 | return -EINVAL; |
437 | |
438 | netlbl_netlink_auditinfo(audit_info: &audit_info); |
439 | |
440 | return netlbl_mgmt_add_common(info, audit_info: &audit_info); |
441 | } |
442 | |
443 | /** |
444 | * netlbl_mgmt_remove - Handle a REMOVE message |
445 | * @skb: the NETLINK buffer |
446 | * @info: the Generic NETLINK info block |
447 | * |
448 | * Description: |
449 | * Process a user generated REMOVE message and remove the specified domain |
450 | * mappings. Returns zero on success, negative values on failure. |
451 | * |
452 | */ |
453 | static int netlbl_mgmt_remove(struct sk_buff *skb, struct genl_info *info) |
454 | { |
455 | char *domain; |
456 | struct netlbl_audit audit_info; |
457 | |
458 | if (!info->attrs[NLBL_MGMT_A_DOMAIN]) |
459 | return -EINVAL; |
460 | |
461 | netlbl_netlink_auditinfo(audit_info: &audit_info); |
462 | |
463 | domain = nla_data(nla: info->attrs[NLBL_MGMT_A_DOMAIN]); |
464 | return netlbl_domhsh_remove(domain, AF_UNSPEC, audit_info: &audit_info); |
465 | } |
466 | |
467 | /** |
468 | * netlbl_mgmt_listall_cb - netlbl_domhsh_walk() callback for LISTALL |
469 | * @entry: the domain mapping hash table entry |
470 | * @arg: the netlbl_domhsh_walk_arg structure |
471 | * |
472 | * Description: |
473 | * This function is designed to be used as a callback to the |
474 | * netlbl_domhsh_walk() function for use in generating a response for a LISTALL |
475 | * message. Returns the size of the message on success, negative values on |
476 | * failure. |
477 | * |
478 | */ |
479 | static int netlbl_mgmt_listall_cb(struct netlbl_dom_map *entry, void *arg) |
480 | { |
481 | int ret_val = -ENOMEM; |
482 | struct netlbl_domhsh_walk_arg *cb_arg = arg; |
483 | void *data; |
484 | |
485 | data = genlmsg_put(skb: cb_arg->skb, NETLINK_CB(cb_arg->nl_cb->skb).portid, |
486 | seq: cb_arg->seq, family: &netlbl_mgmt_gnl_family, |
487 | NLM_F_MULTI, cmd: NLBL_MGMT_C_LISTALL); |
488 | if (data == NULL) |
489 | goto listall_cb_failure; |
490 | |
491 | ret_val = netlbl_mgmt_listentry(skb: cb_arg->skb, entry); |
492 | if (ret_val != 0) |
493 | goto listall_cb_failure; |
494 | |
495 | cb_arg->seq++; |
496 | genlmsg_end(skb: cb_arg->skb, hdr: data); |
497 | return 0; |
498 | |
499 | listall_cb_failure: |
500 | genlmsg_cancel(skb: cb_arg->skb, hdr: data); |
501 | return ret_val; |
502 | } |
503 | |
504 | /** |
505 | * netlbl_mgmt_listall - Handle a LISTALL message |
506 | * @skb: the NETLINK buffer |
507 | * @cb: the NETLINK callback |
508 | * |
509 | * Description: |
510 | * Process a user generated LISTALL message and dumps the domain hash table in |
511 | * a form suitable for use in a kernel generated LISTALL message. Returns zero |
512 | * on success, negative values on failure. |
513 | * |
514 | */ |
515 | static int netlbl_mgmt_listall(struct sk_buff *skb, |
516 | struct netlink_callback *cb) |
517 | { |
518 | struct netlbl_domhsh_walk_arg cb_arg; |
519 | u32 skip_bkt = cb->args[0]; |
520 | u32 skip_chain = cb->args[1]; |
521 | |
522 | cb_arg.nl_cb = cb; |
523 | cb_arg.skb = skb; |
524 | cb_arg.seq = cb->nlh->nlmsg_seq; |
525 | |
526 | netlbl_domhsh_walk(skip_bkt: &skip_bkt, |
527 | skip_chain: &skip_chain, |
528 | callback: netlbl_mgmt_listall_cb, |
529 | cb_arg: &cb_arg); |
530 | |
531 | cb->args[0] = skip_bkt; |
532 | cb->args[1] = skip_chain; |
533 | return skb->len; |
534 | } |
535 | |
536 | /** |
537 | * netlbl_mgmt_adddef - Handle an ADDDEF message |
538 | * @skb: the NETLINK buffer |
539 | * @info: the Generic NETLINK info block |
540 | * |
541 | * Description: |
542 | * Process a user generated ADDDEF message and respond accordingly. Returns |
543 | * zero on success, negative values on failure. |
544 | * |
545 | */ |
546 | static int netlbl_mgmt_adddef(struct sk_buff *skb, struct genl_info *info) |
547 | { |
548 | struct netlbl_audit audit_info; |
549 | |
550 | if ((!info->attrs[NLBL_MGMT_A_PROTOCOL]) || |
551 | (info->attrs[NLBL_MGMT_A_IPV4ADDR] && |
552 | info->attrs[NLBL_MGMT_A_IPV6ADDR]) || |
553 | (info->attrs[NLBL_MGMT_A_IPV4MASK] && |
554 | info->attrs[NLBL_MGMT_A_IPV6MASK]) || |
555 | ((info->attrs[NLBL_MGMT_A_IPV4ADDR] != NULL) ^ |
556 | (info->attrs[NLBL_MGMT_A_IPV4MASK] != NULL)) || |
557 | ((info->attrs[NLBL_MGMT_A_IPV6ADDR] != NULL) ^ |
558 | (info->attrs[NLBL_MGMT_A_IPV6MASK] != NULL))) |
559 | return -EINVAL; |
560 | |
561 | netlbl_netlink_auditinfo(audit_info: &audit_info); |
562 | |
563 | return netlbl_mgmt_add_common(info, audit_info: &audit_info); |
564 | } |
565 | |
566 | /** |
567 | * netlbl_mgmt_removedef - Handle a REMOVEDEF message |
568 | * @skb: the NETLINK buffer |
569 | * @info: the Generic NETLINK info block |
570 | * |
571 | * Description: |
572 | * Process a user generated REMOVEDEF message and remove the default domain |
573 | * mapping. Returns zero on success, negative values on failure. |
574 | * |
575 | */ |
576 | static int netlbl_mgmt_removedef(struct sk_buff *skb, struct genl_info *info) |
577 | { |
578 | struct netlbl_audit audit_info; |
579 | |
580 | netlbl_netlink_auditinfo(audit_info: &audit_info); |
581 | |
582 | return netlbl_domhsh_remove_default(AF_UNSPEC, audit_info: &audit_info); |
583 | } |
584 | |
585 | /** |
586 | * netlbl_mgmt_listdef - Handle a LISTDEF message |
587 | * @skb: the NETLINK buffer |
588 | * @info: the Generic NETLINK info block |
589 | * |
590 | * Description: |
591 | * Process a user generated LISTDEF message and dumps the default domain |
592 | * mapping in a form suitable for use in a kernel generated LISTDEF message. |
593 | * Returns zero on success, negative values on failure. |
594 | * |
595 | */ |
596 | static int netlbl_mgmt_listdef(struct sk_buff *skb, struct genl_info *info) |
597 | { |
598 | int ret_val = -ENOMEM; |
599 | struct sk_buff *ans_skb = NULL; |
600 | void *data; |
601 | struct netlbl_dom_map *entry; |
602 | u16 family; |
603 | |
604 | if (info->attrs[NLBL_MGMT_A_FAMILY]) |
605 | family = nla_get_u16(nla: info->attrs[NLBL_MGMT_A_FAMILY]); |
606 | else |
607 | family = AF_INET; |
608 | |
609 | ans_skb = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); |
610 | if (ans_skb == NULL) |
611 | return -ENOMEM; |
612 | data = genlmsg_put_reply(skb: ans_skb, info, family: &netlbl_mgmt_gnl_family, |
613 | flags: 0, cmd: NLBL_MGMT_C_LISTDEF); |
614 | if (data == NULL) |
615 | goto listdef_failure; |
616 | |
617 | rcu_read_lock(); |
618 | entry = netlbl_domhsh_getentry(NULL, family); |
619 | if (entry == NULL) { |
620 | ret_val = -ENOENT; |
621 | goto listdef_failure_lock; |
622 | } |
623 | ret_val = netlbl_mgmt_listentry(skb: ans_skb, entry); |
624 | rcu_read_unlock(); |
625 | if (ret_val != 0) |
626 | goto listdef_failure; |
627 | |
628 | genlmsg_end(skb: ans_skb, hdr: data); |
629 | return genlmsg_reply(skb: ans_skb, info); |
630 | |
631 | listdef_failure_lock: |
632 | rcu_read_unlock(); |
633 | listdef_failure: |
634 | kfree_skb(skb: ans_skb); |
635 | return ret_val; |
636 | } |
637 | |
638 | /** |
639 | * netlbl_mgmt_protocols_cb - Write an individual PROTOCOL message response |
640 | * @skb: the skb to write to |
641 | * @cb: the NETLINK callback |
642 | * @protocol: the NetLabel protocol to use in the message |
643 | * |
644 | * Description: |
645 | * This function is to be used in conjunction with netlbl_mgmt_protocols() to |
646 | * answer a application's PROTOCOLS message. Returns the size of the message |
647 | * on success, negative values on failure. |
648 | * |
649 | */ |
650 | static int netlbl_mgmt_protocols_cb(struct sk_buff *skb, |
651 | struct netlink_callback *cb, |
652 | u32 protocol) |
653 | { |
654 | int ret_val = -ENOMEM; |
655 | void *data; |
656 | |
657 | data = genlmsg_put(skb, NETLINK_CB(cb->skb).portid, seq: cb->nlh->nlmsg_seq, |
658 | family: &netlbl_mgmt_gnl_family, NLM_F_MULTI, |
659 | cmd: NLBL_MGMT_C_PROTOCOLS); |
660 | if (data == NULL) |
661 | goto protocols_cb_failure; |
662 | |
663 | ret_val = nla_put_u32(skb, attrtype: NLBL_MGMT_A_PROTOCOL, value: protocol); |
664 | if (ret_val != 0) |
665 | goto protocols_cb_failure; |
666 | |
667 | genlmsg_end(skb, hdr: data); |
668 | return 0; |
669 | |
670 | protocols_cb_failure: |
671 | genlmsg_cancel(skb, hdr: data); |
672 | return ret_val; |
673 | } |
674 | |
675 | /** |
676 | * netlbl_mgmt_protocols - Handle a PROTOCOLS message |
677 | * @skb: the NETLINK buffer |
678 | * @cb: the NETLINK callback |
679 | * |
680 | * Description: |
681 | * Process a user generated PROTOCOLS message and respond accordingly. |
682 | * |
683 | */ |
684 | static int netlbl_mgmt_protocols(struct sk_buff *skb, |
685 | struct netlink_callback *cb) |
686 | { |
687 | u32 protos_sent = cb->args[0]; |
688 | |
689 | if (protos_sent == 0) { |
690 | if (netlbl_mgmt_protocols_cb(skb, |
691 | cb, |
692 | NETLBL_NLTYPE_UNLABELED) < 0) |
693 | goto protocols_return; |
694 | protos_sent++; |
695 | } |
696 | if (protos_sent == 1) { |
697 | if (netlbl_mgmt_protocols_cb(skb, |
698 | cb, |
699 | NETLBL_NLTYPE_CIPSOV4) < 0) |
700 | goto protocols_return; |
701 | protos_sent++; |
702 | } |
703 | #if IS_ENABLED(CONFIG_IPV6) |
704 | if (protos_sent == 2) { |
705 | if (netlbl_mgmt_protocols_cb(skb, |
706 | cb, |
707 | NETLBL_NLTYPE_CALIPSO) < 0) |
708 | goto protocols_return; |
709 | protos_sent++; |
710 | } |
711 | #endif |
712 | |
713 | protocols_return: |
714 | cb->args[0] = protos_sent; |
715 | return skb->len; |
716 | } |
717 | |
718 | /** |
719 | * netlbl_mgmt_version - Handle a VERSION message |
720 | * @skb: the NETLINK buffer |
721 | * @info: the Generic NETLINK info block |
722 | * |
723 | * Description: |
724 | * Process a user generated VERSION message and respond accordingly. Returns |
725 | * zero on success, negative values on failure. |
726 | * |
727 | */ |
728 | static int netlbl_mgmt_version(struct sk_buff *skb, struct genl_info *info) |
729 | { |
730 | int ret_val = -ENOMEM; |
731 | struct sk_buff *ans_skb = NULL; |
732 | void *data; |
733 | |
734 | ans_skb = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); |
735 | if (ans_skb == NULL) |
736 | return -ENOMEM; |
737 | data = genlmsg_put_reply(skb: ans_skb, info, family: &netlbl_mgmt_gnl_family, |
738 | flags: 0, cmd: NLBL_MGMT_C_VERSION); |
739 | if (data == NULL) |
740 | goto version_failure; |
741 | |
742 | ret_val = nla_put_u32(skb: ans_skb, |
743 | attrtype: NLBL_MGMT_A_VERSION, |
744 | NETLBL_PROTO_VERSION); |
745 | if (ret_val != 0) |
746 | goto version_failure; |
747 | |
748 | genlmsg_end(skb: ans_skb, hdr: data); |
749 | return genlmsg_reply(skb: ans_skb, info); |
750 | |
751 | version_failure: |
752 | kfree_skb(skb: ans_skb); |
753 | return ret_val; |
754 | } |
755 | |
756 | |
757 | /* |
758 | * NetLabel Generic NETLINK Command Definitions |
759 | */ |
760 | |
761 | static const struct genl_small_ops netlbl_mgmt_genl_ops[] = { |
762 | { |
763 | .cmd = NLBL_MGMT_C_ADD, |
764 | .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, |
765 | .flags = GENL_ADMIN_PERM, |
766 | .doit = netlbl_mgmt_add, |
767 | .dumpit = NULL, |
768 | }, |
769 | { |
770 | .cmd = NLBL_MGMT_C_REMOVE, |
771 | .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, |
772 | .flags = GENL_ADMIN_PERM, |
773 | .doit = netlbl_mgmt_remove, |
774 | .dumpit = NULL, |
775 | }, |
776 | { |
777 | .cmd = NLBL_MGMT_C_LISTALL, |
778 | .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, |
779 | .flags = 0, |
780 | .doit = NULL, |
781 | .dumpit = netlbl_mgmt_listall, |
782 | }, |
783 | { |
784 | .cmd = NLBL_MGMT_C_ADDDEF, |
785 | .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, |
786 | .flags = GENL_ADMIN_PERM, |
787 | .doit = netlbl_mgmt_adddef, |
788 | .dumpit = NULL, |
789 | }, |
790 | { |
791 | .cmd = NLBL_MGMT_C_REMOVEDEF, |
792 | .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, |
793 | .flags = GENL_ADMIN_PERM, |
794 | .doit = netlbl_mgmt_removedef, |
795 | .dumpit = NULL, |
796 | }, |
797 | { |
798 | .cmd = NLBL_MGMT_C_LISTDEF, |
799 | .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, |
800 | .flags = 0, |
801 | .doit = netlbl_mgmt_listdef, |
802 | .dumpit = NULL, |
803 | }, |
804 | { |
805 | .cmd = NLBL_MGMT_C_PROTOCOLS, |
806 | .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, |
807 | .flags = 0, |
808 | .doit = NULL, |
809 | .dumpit = netlbl_mgmt_protocols, |
810 | }, |
811 | { |
812 | .cmd = NLBL_MGMT_C_VERSION, |
813 | .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, |
814 | .flags = 0, |
815 | .doit = netlbl_mgmt_version, |
816 | .dumpit = NULL, |
817 | }, |
818 | }; |
819 | |
820 | static struct genl_family netlbl_mgmt_gnl_family __ro_after_init = { |
821 | .hdrsize = 0, |
822 | .name = NETLBL_NLTYPE_MGMT_NAME, |
823 | .version = NETLBL_PROTO_VERSION, |
824 | .maxattr = NLBL_MGMT_A_MAX, |
825 | .policy = netlbl_mgmt_genl_policy, |
826 | .module = THIS_MODULE, |
827 | .small_ops = netlbl_mgmt_genl_ops, |
828 | .n_small_ops = ARRAY_SIZE(netlbl_mgmt_genl_ops), |
829 | .resv_start_op = NLBL_MGMT_C_VERSION + 1, |
830 | }; |
831 | |
832 | /* |
833 | * NetLabel Generic NETLINK Protocol Functions |
834 | */ |
835 | |
836 | /** |
837 | * netlbl_mgmt_genl_init - Register the NetLabel management component |
838 | * |
839 | * Description: |
840 | * Register the NetLabel management component with the Generic NETLINK |
841 | * mechanism. Returns zero on success, negative values on failure. |
842 | * |
843 | */ |
844 | int __init netlbl_mgmt_genl_init(void) |
845 | { |
846 | return genl_register_family(family: &netlbl_mgmt_gnl_family); |
847 | } |
848 | |