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