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