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