Path: blob/main/sys/security/mac_ipacl/mac_ipacl.c
39476 views
/*-1* Copyright (c) 2003-2004 Networks Associates Technology, Inc.2* Copyright (c) 2006 SPARTA, Inc.3* Copyright (c) 2019, 2023 Shivank Garg <[email protected]>4*5* This software was developed for the FreeBSD Project by Network6* Associates Laboratories, the Security Research Division of Network7* Associates, Inc. under DARPA/SPAWAR contract N66001-01-C-8035 ("CBOSS"),8* as part of the DARPA CHATS research program.9*10* This software was enhanced by SPARTA ISSO under SPAWAR contract11* N66001-04-C-6019 ("SEFOS").12*13* This code was developed as a Google Summer of Code 2019 project14* under the guidance of Bjoern A. Zeeb.15*16* Redistribution and use in source and binary forms, with or without17* modification, are permitted provided that the following conditions18* are met:19* 1. Redistributions of source code must retain the above copyright20* notice, this list of conditions and the following disclaimer.21* 2. Redistributions in binary form must reproduce the above copyright22* notice, this list of conditions and the following disclaimer in the23* documentation and/or other materials provided with the distribution.24*25* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND26* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE27* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE28* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE29* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL30* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS31* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)32* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT33* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY34* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF35* SUCH DAMAGE.36*/3738/*39* The IP address access control policy module - mac_ipacl allows the root of40* the host to limit the VNET jail's privileges of setting IPv4 and IPv641* addresses via sysctl(8) interface. So, the host can define rules for jails42* and their interfaces about IP addresses.43* sysctl(8) is to be used to modify the rules string in following format-44* "jail_id,allow,interface,address_family,IP_addr/prefix_length[@jail_id,...]"45*/4647#include "opt_inet.h"48#include "opt_inet6.h"4950#include <sys/param.h>51#include <sys/module.h>52#include <sys/errno.h>53#include <sys/kernel.h>54#include <sys/mutex.h>55#include <sys/priv.h>56#include <sys/queue.h>57#include <sys/socket.h>58#include <sys/sysctl.h>59#include <sys/systm.h>60#include <sys/types.h>61#include <sys/ucred.h>62#include <sys/jail.h>6364#include <net/if.h>65#include <net/if_var.h>6667#include <netinet/in.h>68#include <netinet6/scope6_var.h>6970#include <security/mac/mac_policy.h>7172static SYSCTL_NODE(_security_mac, OID_AUTO, ipacl, CTLFLAG_RW | CTLFLAG_MPSAFE, 0,73"TrustedBSD mac_ipacl policy controls");7475#ifdef INET76static int ipacl_ipv4 = 1;77SYSCTL_INT(_security_mac_ipacl, OID_AUTO, ipv4, CTLFLAG_RWTUN,78&ipacl_ipv4, 0, "Enforce mac_ipacl for IPv4 addresses");79#endif8081#ifdef INET682static int ipacl_ipv6 = 1;83SYSCTL_INT(_security_mac_ipacl, OID_AUTO, ipv6, CTLFLAG_RWTUN,84&ipacl_ipv6, 0, "Enforce mac_ipacl for IPv6 addresses");85#endif8687static MALLOC_DEFINE(M_IPACL, "ipacl_rule", "Rules for mac_ipacl");8889#define MAC_RULE_STRING_LEN 10249091struct ipacl_addr {92union {93#ifdef INET94struct in_addr ipv4;95#endif96#ifdef INET697struct in6_addr ipv6;98#endif99u_int8_t addr8[16];100u_int16_t addr16[8];101u_int32_t addr32[4];102} ipa; /* 128 bit address*/103#ifdef INET104#define v4 ipa.ipv4105#endif106#ifdef INET6107#define v6 ipa.ipv6108#endif109#define addr8 ipa.addr8110#define addr16 ipa.addr16111#define addr32 ipa.addr32112};113114struct ip_rule {115int jid;116bool allow;117bool subnet_apply; /* Apply rule on whole subnet. */118char if_name[IFNAMSIZ];119int af; /* Address family. */120struct ipacl_addr addr;121struct ipacl_addr mask;122TAILQ_ENTRY(ip_rule) r_entries;123};124125static struct mtx rule_mtx;126static TAILQ_HEAD(rulehead, ip_rule) rule_head;127static char rule_string[MAC_RULE_STRING_LEN];128129static void130destroy_rules(struct rulehead *head)131{132struct ip_rule *rule;133134while ((rule = TAILQ_FIRST(head)) != NULL) {135TAILQ_REMOVE(head, rule, r_entries);136free(rule, M_IPACL);137}138}139140static void141ipacl_init(struct mac_policy_conf *conf)142{143mtx_init(&rule_mtx, "rule_mtx", NULL, MTX_DEF);144TAILQ_INIT(&rule_head);145}146147static void148ipacl_destroy(struct mac_policy_conf *conf)149{150mtx_destroy(&rule_mtx);151destroy_rules(&rule_head);152}153154/*155* Note: parsing routines are destructive on the passed string.156*/157static int158parse_rule_element(char *element, struct ip_rule *rule)159{160char *tok, *p;161int prefix;162#ifdef INET6163int i;164#endif165166/* Should we support a jail wildcard? */167tok = strsep(&element, ",");168if (tok == NULL)169return (EINVAL);170rule->jid = strtol(tok, &p, 10);171if (*p != '\0')172return (EINVAL);173tok = strsep(&element, ",");174if (tok == NULL)175return (EINVAL);176rule->allow = strtol(tok, &p, 10);177if (*p != '\0')178return (EINVAL);179tok = strsep(&element, ",");180if (strlen(tok) + 1 > IFNAMSIZ)181return (EINVAL);182/* Empty interface name is wildcard to all interfaces. */183strlcpy(rule->if_name, tok, strlen(tok) + 1);184tok = strsep(&element, ",");185if (tok == NULL)186return (EINVAL);187rule->af = (strcmp(tok, "AF_INET") == 0) ? AF_INET :188(strcmp(tok, "AF_INET6") == 0) ? AF_INET6 : -1;189if (rule->af == -1)190return (EINVAL);191tok = strsep(&element, "/");192if (tok == NULL)193return (EINVAL);194if (inet_pton(rule->af, tok, rule->addr.addr32) != 1)195return (EINVAL);196tok = element;197if (tok == NULL)198return (EINVAL);199prefix = strtol(tok, &p, 10);200if (*p != '\0')201return (EINVAL);202/* Value -1 for prefix make policy applicable to individual IP only. */203if (prefix == -1)204rule->subnet_apply = false;205else {206rule->subnet_apply = true;207switch (rule->af) {208#ifdef INET209case AF_INET:210if (prefix < 0 || prefix > 32)211return (EINVAL);212213if (prefix == 0)214rule->mask.addr32[0] = htonl(0);215else216rule->mask.addr32[0] =217htonl(~((1 << (32 - prefix)) - 1));218rule->addr.addr32[0] &= rule->mask.addr32[0];219break;220#endif221#ifdef INET6222case AF_INET6:223if (prefix < 0 || prefix > 128)224return (EINVAL);225226for (i = 0; prefix > 0; prefix -= 8, i++)227rule->mask.addr8[i] = prefix >= 8 ? 0xFF :228(u_int8_t)((0xFFU << (8 - prefix)) & 0xFFU);229for (i = 0; i < 16; i++)230rule->addr.addr8[i] &= rule->mask.addr8[i];231break;232#endif233}234}235return (0);236}237238/*239* Format of Rule- jid,allow,interface_name,addr_family,ip_addr/subnet_mask240* Example: sysctl security.mac.ipacl.rules=1,1,epair0b,AF_INET,192.0.2.2/24241*/242static int243parse_rules(char *string, struct rulehead *head)244{245struct ip_rule *new;246char *element;247int error;248249error = 0;250while ((element = strsep(&string, "@")) != NULL) {251if (strlen(element) == 0)252continue;253254new = malloc(sizeof(*new), M_IPACL, M_ZERO | M_WAITOK);255error = parse_rule_element(element, new);256if (error != 0) {257free(new, M_IPACL);258goto out;259}260TAILQ_INSERT_TAIL(head, new, r_entries);261}262out:263if (error != 0)264destroy_rules(head);265return (error);266}267268static int269sysctl_rules(SYSCTL_HANDLER_ARGS)270{271char *string, *copy_string, *new_string;272struct rulehead head, save_head;273int error;274275new_string = NULL;276if (req->newptr != NULL) {277new_string = malloc(MAC_RULE_STRING_LEN, M_IPACL,278M_WAITOK | M_ZERO);279mtx_lock(&rule_mtx);280strcpy(new_string, rule_string);281mtx_unlock(&rule_mtx);282string = new_string;283} else284string = rule_string;285286error = sysctl_handle_string(oidp, string, MAC_RULE_STRING_LEN, req);287if (error)288goto out;289290if (req->newptr != NULL) {291copy_string = strdup(string, M_IPACL);292TAILQ_INIT(&head);293error = parse_rules(copy_string, &head);294free(copy_string, M_IPACL);295if (error)296goto out;297298TAILQ_INIT(&save_head);299mtx_lock(&rule_mtx);300TAILQ_CONCAT(&save_head, &rule_head, r_entries);301TAILQ_CONCAT(&rule_head, &head, r_entries);302strcpy(rule_string, string);303mtx_unlock(&rule_mtx);304destroy_rules(&save_head);305}306out:307if (new_string != NULL)308free(new_string, M_IPACL);309return (error);310}311SYSCTL_PROC(_security_mac_ipacl, OID_AUTO, rules,312CTLTYPE_STRING | CTLFLAG_RW | CTLFLAG_MPSAFE, 0,3130, sysctl_rules, "A", "IP ACL Rules");314315static int316rules_check(struct ucred *cred,317struct ipacl_addr *ip_addr, if_t ifp)318{319struct ip_rule *rule;320int error;321#ifdef INET6322int i;323bool same_subnet;324#endif325326error = EPERM;327328mtx_lock(&rule_mtx);329330/*331* In the case where multiple rules are applicable to an IP address or332* a set of IP addresses, the rule that is defined later in the list333* determines the outcome, disregarding any previous rule for that IP334* address.335* Walk the policy rules list in reverse order until rule applicable336* to the requested IP address is found.337*/338TAILQ_FOREACH_REVERSE(rule, &rule_head, rulehead, r_entries) {339/* Skip if current rule applies to different jail. */340if (cred->cr_prison->pr_id != rule->jid)341continue;342343if (strcmp(rule->if_name, "\0") &&344strcmp(rule->if_name, if_name(ifp)))345continue;346347switch (rule->af) {348#ifdef INET349case AF_INET:350if (rule->subnet_apply) {351if (rule->addr.v4.s_addr !=352(ip_addr->v4.s_addr & rule->mask.v4.s_addr))353continue;354} else355if (ip_addr->v4.s_addr != rule->addr.v4.s_addr)356continue;357break;358#endif359#ifdef INET6360case AF_INET6:361if (rule->subnet_apply) {362same_subnet = true;363for (i = 0; i < 16; i++)364if (rule->addr.v6.s6_addr[i] !=365(ip_addr->v6.s6_addr[i] &366rule->mask.v6.s6_addr[i])) {367same_subnet = false;368break;369}370if (!same_subnet)371continue;372} else373if (bcmp(&rule->addr, ip_addr,374sizeof(*ip_addr)))375continue;376break;377#endif378}379380if (rule->allow)381error = 0;382break;383}384385mtx_unlock(&rule_mtx);386387return (error);388}389390/*391* Feature request: Can we make this sysctl policy apply to jails by default,392* but also allow it to be changed to apply to the base system?393*/394#ifdef INET395static int396ipacl_ip4_check_jail(struct ucred *cred,397const struct in_addr *ia, if_t ifp)398{399struct ipacl_addr ip4_addr;400401ip4_addr.v4 = *ia;402403if (!jailed(cred))404return (0);405406/* Checks with the policy only when it is enforced for ipv4. */407if (ipacl_ipv4)408return rules_check(cred, &ip4_addr, ifp);409410return (0);411}412#endif413414#ifdef INET6415static int416ipacl_ip6_check_jail(struct ucred *cred,417const struct in6_addr *ia6, if_t ifp)418{419struct ipacl_addr ip6_addr;420421ip6_addr.v6 = *ia6; /* Make copy to not alter the original. */422in6_clearscope(&ip6_addr.v6); /* Clear the scope id. */423424if (!jailed(cred))425return (0);426427/* Checks with the policy when it is enforced for ipv6. */428if (ipacl_ipv6)429return rules_check(cred, &ip6_addr, ifp);430431return (0);432}433#endif434435static struct mac_policy_ops ipacl_ops =436{437.mpo_init = ipacl_init,438.mpo_destroy = ipacl_destroy,439#ifdef INET440.mpo_ip4_check_jail = ipacl_ip4_check_jail,441#endif442#ifdef INET6443.mpo_ip6_check_jail = ipacl_ip6_check_jail,444#endif445};446447MAC_POLICY_SET(&ipacl_ops, mac_ipacl, "TrustedBSD MAC/ipacl",448MPC_LOADTIME_FLAG_UNLOADOK, NULL);449450451