1 /*-
2 * Copyright (c) 2003-2004 Networks Associates Technology, Inc.
3 * Copyright (c) 2006 SPARTA, Inc.
4 * Copyright (c) 2019, 2023 Shivank Garg <shivank@FreeBSD.org>
5 *
6 * This software was developed for the FreeBSD Project by Network
7 * Associates Laboratories, the Security Research Division of Network
8 * Associates, Inc. under DARPA/SPAWAR contract N66001-01-C-8035 ("CBOSS"),
9 * as part of the DARPA CHATS research program.
10 *
11 * This software was enhanced by SPARTA ISSO under SPAWAR contract
12 * N66001-04-C-6019 ("SEFOS").
13 *
14 * This code was developed as a Google Summer of Code 2019 project
15 * under the guidance of Bjoern A. Zeeb.
16 *
17 * Redistribution and use in source and binary forms, with or without
18 * modification, are permitted provided that the following conditions
19 * are met:
20 * 1. Redistributions of source code must retain the above copyright
21 * notice, this list of conditions and the following disclaimer.
22 * 2. Redistributions in binary form must reproduce the above copyright
23 * notice, this list of conditions and the following disclaimer in the
24 * documentation and/or other materials provided with the distribution.
25 *
26 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
27 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
28 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
29 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
30 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
31 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
32 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
33 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
34 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
35 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
36 * SUCH DAMAGE.
37 */
38
39 /*
40 * The IP address access control policy module - mac_ipacl allows the root of
41 * the host to limit the VNET jail's privileges of setting IPv4 and IPv6
42 * addresses via sysctl(8) interface. So, the host can define rules for jails
43 * and their interfaces about IP addresses.
44 * sysctl(8) is to be used to modify the rules string in following format-
45 * "jail_id,allow,interface,address_family,IP_addr/prefix_length[@jail_id,...]"
46 */
47
48 #include "opt_inet.h"
49 #include "opt_inet6.h"
50
51 #include <sys/param.h>
52 #include <sys/module.h>
53 #include <sys/errno.h>
54 #include <sys/kernel.h>
55 #include <sys/mutex.h>
56 #include <sys/priv.h>
57 #include <sys/queue.h>
58 #include <sys/socket.h>
59 #include <sys/sysctl.h>
60 #include <sys/systm.h>
61 #include <sys/types.h>
62 #include <sys/ucred.h>
63 #include <sys/jail.h>
64
65 #include <net/if.h>
66 #include <net/if_var.h>
67
68 #include <netinet/in.h>
69 #include <netinet6/scope6_var.h>
70
71 #include <security/mac/mac_policy.h>
72
73 static SYSCTL_NODE(_security_mac, OID_AUTO, ipacl, CTLFLAG_RW | CTLFLAG_MPSAFE, 0,
74 "TrustedBSD mac_ipacl policy controls");
75
76 #ifdef INET
77 static int ipacl_ipv4 = 1;
78 SYSCTL_INT(_security_mac_ipacl, OID_AUTO, ipv4, CTLFLAG_RWTUN,
79 &ipacl_ipv4, 0, "Enforce mac_ipacl for IPv4 addresses");
80 #endif
81
82 #ifdef INET6
83 static int ipacl_ipv6 = 1;
84 SYSCTL_INT(_security_mac_ipacl, OID_AUTO, ipv6, CTLFLAG_RWTUN,
85 &ipacl_ipv6, 0, "Enforce mac_ipacl for IPv6 addresses");
86 #endif
87
88 static MALLOC_DEFINE(M_IPACL, "ipacl_rule", "Rules for mac_ipacl");
89
90 #define MAC_RULE_STRING_LEN 1024
91
92 struct ipacl_addr {
93 union {
94 #ifdef INET
95 struct in_addr ipv4;
96 #endif
97 #ifdef INET6
98 struct in6_addr ipv6;
99 #endif
100 u_int8_t addr8[16];
101 u_int16_t addr16[8];
102 u_int32_t addr32[4];
103 } ipa; /* 128 bit address*/
104 #ifdef INET
105 #define v4 ipa.ipv4
106 #endif
107 #ifdef INET6
108 #define v6 ipa.ipv6
109 #endif
110 #define addr8 ipa.addr8
111 #define addr16 ipa.addr16
112 #define addr32 ipa.addr32
113 };
114
115 struct ip_rule {
116 int jid;
117 bool allow;
118 bool subnet_apply; /* Apply rule on whole subnet. */
119 char if_name[IFNAMSIZ];
120 int af; /* Address family. */
121 struct ipacl_addr addr;
122 struct ipacl_addr mask;
123 TAILQ_ENTRY(ip_rule) r_entries;
124 };
125
126 static struct mtx rule_mtx;
127 static TAILQ_HEAD(rulehead, ip_rule) rule_head;
128 static char rule_string[MAC_RULE_STRING_LEN];
129
130 static void
destroy_rules(struct rulehead * head)131 destroy_rules(struct rulehead *head)
132 {
133 struct ip_rule *rule;
134
135 while ((rule = TAILQ_FIRST(head)) != NULL) {
136 TAILQ_REMOVE(head, rule, r_entries);
137 free(rule, M_IPACL);
138 }
139 }
140
141 static void
ipacl_init(struct mac_policy_conf * conf)142 ipacl_init(struct mac_policy_conf *conf)
143 {
144 mtx_init(&rule_mtx, "rule_mtx", NULL, MTX_DEF);
145 TAILQ_INIT(&rule_head);
146 }
147
148 static void
ipacl_destroy(struct mac_policy_conf * conf)149 ipacl_destroy(struct mac_policy_conf *conf)
150 {
151 mtx_destroy(&rule_mtx);
152 destroy_rules(&rule_head);
153 }
154
155 /*
156 * Note: parsing routines are destructive on the passed string.
157 */
158 static int
parse_rule_element(char * element,struct ip_rule * rule)159 parse_rule_element(char *element, struct ip_rule *rule)
160 {
161 char *tok, *p;
162 int prefix;
163 #ifdef INET6
164 int i;
165 #endif
166
167 /* Should we support a jail wildcard? */
168 tok = strsep(&element, ",");
169 if (tok == NULL)
170 return (EINVAL);
171 rule->jid = strtol(tok, &p, 10);
172 if (*p != '\0')
173 return (EINVAL);
174 tok = strsep(&element, ",");
175 if (tok == NULL)
176 return (EINVAL);
177 rule->allow = strtol(tok, &p, 10);
178 if (*p != '\0')
179 return (EINVAL);
180 tok = strsep(&element, ",");
181 if (strlen(tok) + 1 > IFNAMSIZ)
182 return (EINVAL);
183 /* Empty interface name is wildcard to all interfaces. */
184 strlcpy(rule->if_name, tok, strlen(tok) + 1);
185 tok = strsep(&element, ",");
186 if (tok == NULL)
187 return (EINVAL);
188 rule->af = (strcmp(tok, "AF_INET") == 0) ? AF_INET :
189 (strcmp(tok, "AF_INET6") == 0) ? AF_INET6 : -1;
190 if (rule->af == -1)
191 return (EINVAL);
192 tok = strsep(&element, "/");
193 if (tok == NULL)
194 return (EINVAL);
195 if (inet_pton(rule->af, tok, rule->addr.addr32) != 1)
196 return (EINVAL);
197 tok = element;
198 if (tok == NULL)
199 return (EINVAL);
200 prefix = strtol(tok, &p, 10);
201 if (*p != '\0')
202 return (EINVAL);
203 /* Value -1 for prefix make policy applicable to individual IP only. */
204 if (prefix == -1)
205 rule->subnet_apply = false;
206 else {
207 rule->subnet_apply = true;
208 switch (rule->af) {
209 #ifdef INET
210 case AF_INET:
211 if (prefix < 0 || prefix > 32)
212 return (EINVAL);
213
214 if (prefix == 0)
215 rule->mask.addr32[0] = htonl(0);
216 else
217 rule->mask.addr32[0] =
218 htonl(~((1 << (32 - prefix)) - 1));
219 rule->addr.addr32[0] &= rule->mask.addr32[0];
220 break;
221 #endif
222 #ifdef INET6
223 case AF_INET6:
224 if (prefix < 0 || prefix > 128)
225 return (EINVAL);
226
227 for (i = 0; prefix > 0; prefix -= 8, i++)
228 rule->mask.addr8[i] = prefix >= 8 ? 0xFF :
229 (u_int8_t)((0xFFU << (8 - prefix)) & 0xFFU);
230 for (i = 0; i < 16; i++)
231 rule->addr.addr8[i] &= rule->mask.addr8[i];
232 break;
233 #endif
234 }
235 }
236 return (0);
237 }
238
239 /*
240 * Format of Rule- jid,allow,interface_name,addr_family,ip_addr/subnet_mask
241 * Example: sysctl security.mac.ipacl.rules=1,1,epair0b,AF_INET,192.0.2.2/24
242 */
243 static int
parse_rules(char * string,struct rulehead * head)244 parse_rules(char *string, struct rulehead *head)
245 {
246 struct ip_rule *new;
247 char *element;
248 int error;
249
250 error = 0;
251 while ((element = strsep(&string, "@")) != NULL) {
252 if (strlen(element) == 0)
253 continue;
254
255 new = malloc(sizeof(*new), M_IPACL, M_ZERO | M_WAITOK);
256 error = parse_rule_element(element, new);
257 if (error != 0) {
258 free(new, M_IPACL);
259 goto out;
260 }
261 TAILQ_INSERT_TAIL(head, new, r_entries);
262 }
263 out:
264 if (error != 0)
265 destroy_rules(head);
266 return (error);
267 }
268
269 static int
sysctl_rules(SYSCTL_HANDLER_ARGS)270 sysctl_rules(SYSCTL_HANDLER_ARGS)
271 {
272 char *string, *copy_string, *new_string;
273 struct rulehead head, save_head;
274 int error;
275
276 new_string = NULL;
277 if (req->newptr != NULL) {
278 new_string = malloc(MAC_RULE_STRING_LEN, M_IPACL,
279 M_WAITOK | M_ZERO);
280 mtx_lock(&rule_mtx);
281 strcpy(new_string, rule_string);
282 mtx_unlock(&rule_mtx);
283 string = new_string;
284 } else
285 string = rule_string;
286
287 error = sysctl_handle_string(oidp, string, MAC_RULE_STRING_LEN, req);
288 if (error)
289 goto out;
290
291 if (req->newptr != NULL) {
292 copy_string = strdup(string, M_IPACL);
293 TAILQ_INIT(&head);
294 error = parse_rules(copy_string, &head);
295 free(copy_string, M_IPACL);
296 if (error)
297 goto out;
298
299 TAILQ_INIT(&save_head);
300 mtx_lock(&rule_mtx);
301 TAILQ_CONCAT(&save_head, &rule_head, r_entries);
302 TAILQ_CONCAT(&rule_head, &head, r_entries);
303 strcpy(rule_string, string);
304 mtx_unlock(&rule_mtx);
305 destroy_rules(&save_head);
306 }
307 out:
308 if (new_string != NULL)
309 free(new_string, M_IPACL);
310 return (error);
311 }
312 SYSCTL_PROC(_security_mac_ipacl, OID_AUTO, rules,
313 CTLTYPE_STRING | CTLFLAG_RW | CTLFLAG_MPSAFE, 0,
314 0, sysctl_rules, "A", "IP ACL Rules");
315
316 static int
rules_check(struct ucred * cred,struct ipacl_addr * ip_addr,if_t ifp)317 rules_check(struct ucred *cred,
318 struct ipacl_addr *ip_addr, if_t ifp)
319 {
320 struct ip_rule *rule;
321 int error;
322 #ifdef INET6
323 int i;
324 bool same_subnet;
325 #endif
326
327 error = EPERM;
328
329 mtx_lock(&rule_mtx);
330
331 /*
332 * In the case where multiple rules are applicable to an IP address or
333 * a set of IP addresses, the rule that is defined later in the list
334 * determines the outcome, disregarding any previous rule for that IP
335 * address.
336 * Walk the policy rules list in reverse order until rule applicable
337 * to the requested IP address is found.
338 */
339 TAILQ_FOREACH_REVERSE(rule, &rule_head, rulehead, r_entries) {
340 /* Skip if current rule applies to different jail. */
341 if (cred->cr_prison->pr_id != rule->jid)
342 continue;
343
344 if (strcmp(rule->if_name, "\0") &&
345 strcmp(rule->if_name, if_name(ifp)))
346 continue;
347
348 switch (rule->af) {
349 #ifdef INET
350 case AF_INET:
351 if (rule->subnet_apply) {
352 if (rule->addr.v4.s_addr !=
353 (ip_addr->v4.s_addr & rule->mask.v4.s_addr))
354 continue;
355 } else
356 if (ip_addr->v4.s_addr != rule->addr.v4.s_addr)
357 continue;
358 break;
359 #endif
360 #ifdef INET6
361 case AF_INET6:
362 if (rule->subnet_apply) {
363 same_subnet = true;
364 for (i = 0; i < 16; i++)
365 if (rule->addr.v6.s6_addr[i] !=
366 (ip_addr->v6.s6_addr[i] &
367 rule->mask.v6.s6_addr[i])) {
368 same_subnet = false;
369 break;
370 }
371 if (!same_subnet)
372 continue;
373 } else
374 if (bcmp(&rule->addr, ip_addr,
375 sizeof(*ip_addr)))
376 continue;
377 break;
378 #endif
379 }
380
381 if (rule->allow)
382 error = 0;
383 break;
384 }
385
386 mtx_unlock(&rule_mtx);
387
388 return (error);
389 }
390
391 /*
392 * Feature request: Can we make this sysctl policy apply to jails by default,
393 * but also allow it to be changed to apply to the base system?
394 */
395 #ifdef INET
396 static int
ipacl_ip4_check_jail(struct ucred * cred,const struct in_addr * ia,if_t ifp)397 ipacl_ip4_check_jail(struct ucred *cred,
398 const struct in_addr *ia, if_t ifp)
399 {
400 struct ipacl_addr ip4_addr;
401
402 ip4_addr.v4 = *ia;
403
404 if (!jailed(cred))
405 return (0);
406
407 /* Checks with the policy only when it is enforced for ipv4. */
408 if (ipacl_ipv4)
409 return rules_check(cred, &ip4_addr, ifp);
410
411 return (0);
412 }
413 #endif
414
415 #ifdef INET6
416 static int
ipacl_ip6_check_jail(struct ucred * cred,const struct in6_addr * ia6,if_t ifp)417 ipacl_ip6_check_jail(struct ucred *cred,
418 const struct in6_addr *ia6, if_t ifp)
419 {
420 struct ipacl_addr ip6_addr;
421
422 ip6_addr.v6 = *ia6; /* Make copy to not alter the original. */
423 in6_clearscope(&ip6_addr.v6); /* Clear the scope id. */
424
425 if (!jailed(cred))
426 return (0);
427
428 /* Checks with the policy when it is enforced for ipv6. */
429 if (ipacl_ipv6)
430 return rules_check(cred, &ip6_addr, ifp);
431
432 return (0);
433 }
434 #endif
435
436 static struct mac_policy_ops ipacl_ops =
437 {
438 .mpo_init = ipacl_init,
439 .mpo_destroy = ipacl_destroy,
440 #ifdef INET
441 .mpo_ip4_check_jail = ipacl_ip4_check_jail,
442 #endif
443 #ifdef INET6
444 .mpo_ip6_check_jail = ipacl_ip6_check_jail,
445 #endif
446 };
447
448 MAC_POLICY_SET(&ipacl_ops, mac_ipacl, "TrustedBSD MAC/ipacl",
449 MPC_LOADTIME_FLAG_UNLOADOK, NULL);
450