xref: /freebsd/sys/security/mac_ipacl/mac_ipacl.c (revision 02e9120893770924227138ba49df1edb3896112a)
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 SYSCTL_DECL(_security_mac);
74 
75 static SYSCTL_NODE(_security_mac, OID_AUTO, ipacl, CTLFLAG_RW | CTLFLAG_MPSAFE, 0,
76     "TrustedBSD mac_ipacl policy controls");
77 
78 #ifdef INET
79 static int ipacl_ipv4 = 1;
80 SYSCTL_INT(_security_mac_ipacl, OID_AUTO, ipv4, CTLFLAG_RWTUN,
81     &ipacl_ipv4, 0, "Enforce mac_ipacl for IPv4 addresses");
82 #endif
83 
84 #ifdef INET6
85 static int ipacl_ipv6 = 1;
86 SYSCTL_INT(_security_mac_ipacl, OID_AUTO, ipv6, CTLFLAG_RWTUN,
87     &ipacl_ipv6, 0, "Enforce mac_ipacl for IPv6 addresses");
88 #endif
89 
90 static MALLOC_DEFINE(M_IPACL, "ipacl_rule", "Rules for mac_ipacl");
91 
92 #define	MAC_RULE_STRING_LEN	1024
93 
94 struct ipacl_addr {
95 	union {
96 #ifdef INET
97 		struct in_addr	ipv4;
98 #endif
99 #ifdef INET6
100 		struct in6_addr	ipv6;
101 #endif
102 		u_int8_t	addr8[16];
103 		u_int16_t	addr16[8];
104 		u_int32_t	addr32[4];
105 	} ipa; /* 128 bit address*/
106 #ifdef INET
107 #define v4	ipa.ipv4
108 #endif
109 #ifdef INET6
110 #define v6	ipa.ipv6
111 #endif
112 #define addr8	ipa.addr8
113 #define addr16	ipa.addr16
114 #define addr32	ipa.addr32
115 };
116 
117 struct ip_rule {
118 	int			jid;
119 	bool			allow;
120 	bool			subnet_apply; /* Apply rule on whole subnet. */
121 	char			if_name[IFNAMSIZ];
122 	int			af; /* Address family. */
123 	struct	ipacl_addr	addr;
124 	struct	ipacl_addr	mask;
125 	TAILQ_ENTRY(ip_rule)	r_entries;
126 };
127 
128 static struct mtx			rule_mtx;
129 static TAILQ_HEAD(rulehead, ip_rule)	rule_head;
130 static char				rule_string[MAC_RULE_STRING_LEN];
131 
132 static void
133 destroy_rules(struct rulehead *head)
134 {
135 	struct ip_rule *rule;
136 
137 	while ((rule = TAILQ_FIRST(head)) != NULL) {
138 		TAILQ_REMOVE(head, rule, r_entries);
139 		free(rule, M_IPACL);
140 	}
141 }
142 
143 static void
144 ipacl_init(struct mac_policy_conf *conf)
145 {
146 	mtx_init(&rule_mtx, "rule_mtx", NULL, MTX_DEF);
147 	TAILQ_INIT(&rule_head);
148 }
149 
150 static void
151 ipacl_destroy(struct mac_policy_conf *conf)
152 {
153 	mtx_destroy(&rule_mtx);
154 	destroy_rules(&rule_head);
155 }
156 
157 /*
158  * Note: parsing routines are destructive on the passed string.
159  */
160 static int
161 parse_rule_element(char *element, struct ip_rule *rule)
162 {
163 	char *tok, *p;
164 	int prefix;
165 #ifdef INET6
166 	int i;
167 #endif
168 
169 	/* Should we support a jail wildcard? */
170 	tok = strsep(&element, ",");
171 	if (tok == NULL)
172 		return (EINVAL);
173 	rule->jid = strtol(tok, &p, 10);
174 	if (*p != '\0')
175 		return (EINVAL);
176 	tok = strsep(&element, ",");
177 	if (tok == NULL)
178 		return (EINVAL);
179 	rule->allow = strtol(tok, &p, 10);
180 	if (*p != '\0')
181 		return (EINVAL);
182 	tok = strsep(&element, ",");
183 	if (strlen(tok) + 1 > IFNAMSIZ)
184 		return (EINVAL);
185 	/* Empty interface name is wildcard to all interfaces. */
186 	strlcpy(rule->if_name, tok, strlen(tok) + 1);
187 	tok = strsep(&element, ",");
188 	if (tok == NULL)
189 		return (EINVAL);
190 	rule->af = (strcmp(tok, "AF_INET") == 0) ? AF_INET :
191 	    (strcmp(tok, "AF_INET6") == 0) ? AF_INET6 : -1;
192 	if (rule->af == -1)
193 		return (EINVAL);
194 	tok = strsep(&element, "/");
195 	if (tok == NULL)
196 		return (EINVAL);
197 	if (inet_pton(rule->af, tok, rule->addr.addr32) != 1)
198 		return (EINVAL);
199 	tok = element;
200 	if (tok == NULL)
201 		return (EINVAL);
202 	prefix = strtol(tok, &p, 10);
203 	if (*p != '\0')
204 		return (EINVAL);
205 	/* Value -1 for prefix make policy applicable to individual IP only. */
206 	if (prefix == -1)
207 		rule->subnet_apply = false;
208 	else {
209 		rule->subnet_apply = true;
210 		switch (rule->af) {
211 #ifdef INET
212 		case AF_INET:
213 			if (prefix < 0 || prefix > 32)
214 				return (EINVAL);
215 
216 			if (prefix == 0)
217 				rule->mask.addr32[0] = htonl(0);
218 			else
219 				rule->mask.addr32[0] =
220 				    htonl(~((1 << (32 - prefix)) - 1));
221 			rule->addr.addr32[0] &= rule->mask.addr32[0];
222 			break;
223 #endif
224 #ifdef INET6
225 		case AF_INET6:
226 			if (prefix < 0 || prefix > 128)
227 				return (EINVAL);
228 
229 			for (i = 0; prefix > 0; prefix -= 8, i++)
230 				rule->mask.addr8[i] = prefix >= 8 ? 0xFF :
231 				    (u_int8_t)((0xFFU << (8 - prefix)) & 0xFFU);
232 			for (i = 0; i < 16; i++)
233 				rule->addr.addr8[i] &= rule->mask.addr8[i];
234 			break;
235 #endif
236 		}
237 	}
238 	return (0);
239 }
240 
241 /*
242  * Format of Rule- jid,allow,interface_name,addr_family,ip_addr/subnet_mask
243  * Example: sysctl security.mac.ipacl.rules=1,1,epair0b,AF_INET,192.0.2.2/24
244  */
245 static int
246 parse_rules(char *string, struct rulehead *head)
247 {
248 	struct ip_rule *new;
249 	char *element;
250 	int error;
251 
252 	error = 0;
253 	while ((element = strsep(&string, "@")) != NULL) {
254 		if (strlen(element) == 0)
255 			continue;
256 
257 		new = malloc(sizeof(*new), M_IPACL, M_ZERO | M_WAITOK);
258 		error = parse_rule_element(element, new);
259 		if (error != 0) {
260 			free(new, M_IPACL);
261 			goto out;
262 		}
263 		TAILQ_INSERT_TAIL(head, new, r_entries);
264 	}
265 out:
266 	if (error != 0)
267 		destroy_rules(head);
268 	return (error);
269 }
270 
271 static int
272 sysctl_rules(SYSCTL_HANDLER_ARGS)
273 {
274 	char *string, *copy_string, *new_string;
275 	struct rulehead head, save_head;
276 	int error;
277 
278 	new_string = NULL;
279 	if (req->newptr != NULL) {
280 		new_string = malloc(MAC_RULE_STRING_LEN, M_IPACL,
281 		    M_WAITOK | M_ZERO);
282 		mtx_lock(&rule_mtx);
283 		strcpy(new_string, rule_string);
284 		mtx_unlock(&rule_mtx);
285 		string = new_string;
286 	} else
287 		string = rule_string;
288 
289 	error = sysctl_handle_string(oidp, string, MAC_RULE_STRING_LEN, req);
290 	if (error)
291 		goto out;
292 
293 	if (req->newptr != NULL) {
294 		copy_string = strdup(string, M_IPACL);
295 		TAILQ_INIT(&head);
296 		error = parse_rules(copy_string, &head);
297 		free(copy_string, M_IPACL);
298 		if (error)
299 			goto out;
300 
301 		TAILQ_INIT(&save_head);
302 		mtx_lock(&rule_mtx);
303 		TAILQ_CONCAT(&save_head, &rule_head, r_entries);
304 		TAILQ_CONCAT(&rule_head, &head, r_entries);
305 		strcpy(rule_string, string);
306 		mtx_unlock(&rule_mtx);
307 		destroy_rules(&save_head);
308 	}
309 out:
310 	if (new_string != NULL)
311 		free(new_string, M_IPACL);
312 	return (error);
313 }
314 SYSCTL_PROC(_security_mac_ipacl, OID_AUTO, rules,
315     CTLTYPE_STRING | CTLFLAG_RW | CTLFLAG_MPSAFE, 0,
316     0, sysctl_rules, "A", "IP ACL Rules");
317 
318 static int
319 rules_check(struct ucred *cred,
320     struct ipacl_addr *ip_addr, if_t ifp)
321 {
322 	struct ip_rule *rule;
323 	int error;
324 #ifdef INET6
325 	int i;
326 	bool same_subnet;
327 #endif
328 
329 	error = EPERM;
330 
331 	mtx_lock(&rule_mtx);
332 
333 	/*
334 	 * In the case where multiple rules are applicable to an IP address or
335 	 * a set of IP addresses, the rule that is defined later in the list
336 	 * determines the outcome, disregarding any previous rule for that IP
337 	 * address.
338 	 * Walk the policy rules list in reverse order until rule applicable
339 	 * to the requested IP address is found.
340 	 */
341 	TAILQ_FOREACH_REVERSE(rule, &rule_head, rulehead, r_entries) {
342 		/* Skip if current rule applies to different jail. */
343 		if (cred->cr_prison->pr_id != rule->jid)
344 			continue;
345 
346 		if (strcmp(rule->if_name, "\0") &&
347 		    strcmp(rule->if_name, if_name(ifp)))
348 			continue;
349 
350 		switch (rule->af) {
351 #ifdef INET
352 		case AF_INET:
353 			if (rule->subnet_apply) {
354 				if (rule->addr.v4.s_addr !=
355 				    (ip_addr->v4.s_addr & rule->mask.v4.s_addr))
356 					continue;
357 			} else
358 				if (ip_addr->v4.s_addr != rule->addr.v4.s_addr)
359 					continue;
360 			break;
361 #endif
362 #ifdef INET6
363 		case AF_INET6:
364 			if (rule->subnet_apply) {
365 				same_subnet = true;
366 				for (i = 0; i < 16; i++)
367 					if (rule->addr.v6.s6_addr[i] !=
368 					    (ip_addr->v6.s6_addr[i] &
369 					    rule->mask.v6.s6_addr[i])) {
370 						same_subnet = false;
371 						break;
372 					}
373 				if (!same_subnet)
374 					continue;
375 			} else
376 				if (bcmp(&rule->addr, ip_addr,
377 				    sizeof(*ip_addr)))
378 					continue;
379 			break;
380 #endif
381 		}
382 
383 		if (rule->allow)
384 			error = 0;
385 		break;
386 	}
387 
388 	mtx_unlock(&rule_mtx);
389 
390 	return (error);
391 }
392 
393 /*
394  * Feature request: Can we make this sysctl policy apply to jails by default,
395  * but also allow it to be changed to apply to the base system?
396  */
397 #ifdef INET
398 static int
399 ipacl_ip4_check_jail(struct ucred *cred,
400     const struct in_addr *ia, if_t ifp)
401 {
402 	struct ipacl_addr ip4_addr;
403 
404 	ip4_addr.v4 = *ia;
405 
406 	if (!jailed(cred))
407 		return (0);
408 
409 	/* Checks with the policy only when it is enforced for ipv4. */
410 	if (ipacl_ipv4)
411 		return rules_check(cred, &ip4_addr, ifp);
412 
413 	return (0);
414 }
415 #endif
416 
417 #ifdef INET6
418 static int
419 ipacl_ip6_check_jail(struct ucred *cred,
420     const struct in6_addr *ia6, if_t ifp)
421 {
422 	struct ipacl_addr ip6_addr;
423 
424 	ip6_addr.v6 = *ia6; /* Make copy to not alter the original. */
425 	in6_clearscope(&ip6_addr.v6); /* Clear the scope id. */
426 
427 	if (!jailed(cred))
428 		return (0);
429 
430 	/* Checks with the policy when it is enforced for ipv6. */
431 	if (ipacl_ipv6)
432 		return rules_check(cred, &ip6_addr, ifp);
433 
434 	return (0);
435 }
436 #endif
437 
438 static struct mac_policy_ops ipacl_ops =
439 {
440 	.mpo_init = ipacl_init,
441 	.mpo_destroy = ipacl_destroy,
442 #ifdef INET
443 	.mpo_ip4_check_jail = ipacl_ip4_check_jail,
444 #endif
445 #ifdef INET6
446 	.mpo_ip6_check_jail = ipacl_ip6_check_jail,
447 #endif
448 };
449 
450 MAC_POLICY_SET(&ipacl_ops, mac_ipacl, "TrustedBSD MAC/ipacl",
451     MPC_LOADTIME_FLAG_UNLOADOK, NULL);
452