diff --git a/etc/mtree/BSD.tests.dist b/etc/mtree/BSD.tests.dist --- a/etc/mtree/BSD.tests.dist +++ b/etc/mtree/BSD.tests.dist @@ -815,6 +815,8 @@ mac bsdextended .. + ipacl + .. portacl .. .. diff --git a/share/man/man4/Makefile b/share/man/man4/Makefile --- a/share/man/man4/Makefile +++ b/share/man/man4/Makefile @@ -292,6 +292,7 @@ mac_bsdextended.4 \ mac_ddb.4 \ mac_ifoff.4 \ + mac_ipacl.4 \ mac_lomac.4 \ mac_mls.4 \ mac_none.4 \ diff --git a/share/man/man4/mac.4 b/share/man/man4/mac.4 --- a/share/man/man4/mac.4 +++ b/share/man/man4/mac.4 @@ -30,7 +30,7 @@ .\" .\" $FreeBSD$ .\" -.Dd June 10, 2023 +.Dd July 25, 2023 .Dt MAC 4 .Os .Sh NAME @@ -57,6 +57,7 @@ .It Xr mac_bsdextended 4 Ta "File system firewall" Ta no Ta any time .It Xr mac_ddb 4 Ta "ddb(4) interface restrictions" Ta no Ta any time .It Xr mac_ifoff 4 Ta "Interface silencing" Ta no Ta any time +.It Xr mac_ipacl 4 Ta "IP Address access control" Ta no Ta any time .It Xr mac_lomac 4 Ta "Low-Watermark MAC policy" Ta yes Ta boot only .It Xr mac_mls 4 Ta "Confidentiality policy" Ta yes Ta boot only .It Xr mac_ntpd 4 Ta "Non-root NTP Daemon policy" Ta no Ta any time @@ -205,6 +206,7 @@ .Xr mac_bsdextended 4 , .Xr mac_ddb 4 , .Xr mac_ifoff 4 , +.Xr mac_ipacl 4 , .Xr mac_lomac 4 , .Xr mac_mls 4 , .Xr mac_none 4 , diff --git a/share/man/man4/mac_ipacl.4 b/share/man/man4/mac_ipacl.4 new file mode 100644 --- /dev/null +++ b/share/man/man4/mac_ipacl.4 @@ -0,0 +1,166 @@ +.\" Copyright (c) 2019, 2023 Shivank Garg +.\" +.\" This code was developed as a Google Summer of Code 2019 project +.\" under the guidance of Bjoern A. Zeeb. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" $FreeBSD$ +.\" +.Dd July 25, 2023 +.Dt MAC_IPACL 4 +.Os +.Sh NAME +.Nm mac_ipacl +.Nd "IP Address access control policy" +.Sh SYNOPSIS +Add the following lines in your kernel configuration file to compile the +IP address access control policy into your kernel: +.Bd -ragged -offset indent +.Cd "options MAC" +.Cd "options MAC_IPACL" +.Ed +.Pp +To load the mac_ipacl policy module at boot time, add the +following line in your kernel configuration file: +.Bd -ragged -offset indent +.Cd "options MAC" +.Ed +.Pp +and in +.Xr loader.conf 5 add: +.Pp +.Dl "mac_ipacl_load=""YES""" +.Sh DESCRIPTION +The +.Nm +policy allows the root of the host to use the +.Xr sysctl 8 +interface to limit the +.Xr VNET 9 +jail's ability to set IPv4 and IPv6 addresses. +So, the host can +define rules for jails and their interfaces about IP addresses +with +.Xr sysctl 8 +MIBs. +.Pp +Its default behavior is to deny all IP addresses for the jail if +.Nm +policy is enforced and allow/deny IP (or subnets) according to the +.Va security.mac.ipacl.rules +string specified with +.Xr sysctl 8 +.Ss Runtime Configuration +The following +.Xr sysctl 8 +MIBs are used to control enforcement and behavior of this MAC Policy. +.Bl -tag -width indent +.It Va security.mac.ipacl.ipv4 +Enforce +.Nm +for IPv4 addresses. +(Default: 1). +.It Va security.mac.ipacl.ipv6 +Enforce +.Nm +for IPv6 addresses. +(Default: 1). +.It Va security.mac.ipacl.rules +The IP address access control list is specified in the following format: +.Pp +.Sm off +.D1 jid , allow , interface , addr_family , IP_addr / prefix Op @ jid , ... +.Sm on +.Bl -tag -width "interface" +.It jid +Describe the jail id of the jail for which the rule is written. +.It allow +1 for allow and 0 for deny. +Decides action performed for the rule. +.It interface +Name of the interface the rule is enforced for. +If the interface is left empty then it is a wildcard to enforce the +rule for all interfaces. +.It addr_family +Address family of the IP_addr. +The input to be given as AF_INET or AF_INET6 +string only. +.It IP_addr +IP address (or subnet) to be allowed/denied. +Action depends on the prefix length. +.It prefix +Prefix length of the subnet to be enforced by the policy. +-1 implies the policy is enforced for the individual IP address. +For a non-negative value, a range of IP addresses (present in subnet) +which is calculated as subnet = IP_addr & mask. +.El +.El +.Sh EXAMPLES +Behavior of the +.Nm +policy module for different inputs of sysctl variable: +.Bl -tag -width "1." +.It 1. +Assign ipv4=1, ipv6=0 and rules="1,1,,AF_INET,169.254.123.123/-1" +.Pp +It allow only 169.254.123.123 IPv4 address for all interfaces (wildcard) of jail 1. +It allows all IPv6 addresses since the policy is not enforced for IPv6. +.It 2. +Assign ipv4=1, ipv6=1 and rules="1,1,epair0b,AF_INET6,fe80::/32@1,0,epair0b,AF_INET6,fe80::abcd/-1" +.Pp +It denies all IPv4 addresses as the policy is enforced but no rules are specified +about it. +It allows all IPv6 addresses in subnet fe80::/32 except +fe80::abcd for interface epair0b only. +.It 3. +Assign ipv4=1, ipv6=1, rules="2,1,,AF_INET6,fc00::/7@2,0,,AF_INET6,fc00::1111:2200/120@2,1,,AF_INET6,fc00::1111:2299/-1@1,1,,AF_INET,198.51.100.0/24" +.Pp +It allows IPv4 in subnet 198.51.100.0/24 for jail 2 and +all interfaces. +It allows IPv6 addresses in subnet fc00::/7 but +denies subnet fc00::1111:2200/120, and allows individual IP +fc00::1111:2299 from the denied subnet for all interfaces in jail 2. +.El +Please refer to mac/ipacl tests-framework for wide variety of examples on using +the ipacl module. +.Sh LIMITATIONS/PRECAUTIONS +In the case where multiple rules are applicable to an IP address or +a set of IP addresses, the rule that is defined later in the list +determines the outcome, disregarding any previous rule for that IP +address. +.Sh FUTURE WORKS +Rules are given with sysctl interface which gets very complex to give them +all in command line. +It has to be simplified with a better way to input those rules. +.Sh SEE ALSO +.Xr mac 4 , +.Xr mac 9 +.Sh AUTHORS +The +.Nm +policy module was developed as a Google Summer of Code Project in 2019 +by +.An -nosplit +.An "Shivank Garg" Aq Mt shivank@FreeBSD.org +under the guidance of +.An "Bjoern A. Zeeb" Aq Mt bz@FreeBSD.org . diff --git a/sys/conf/NOTES b/sys/conf/NOTES --- a/sys/conf/NOTES +++ b/sys/conf/NOTES @@ -1232,6 +1232,7 @@ options MAC_BSDEXTENDED options MAC_DDB options MAC_IFOFF +options MAC_IPACL options MAC_LOMAC options MAC_MLS options MAC_NONE diff --git a/sys/conf/files b/sys/conf/files --- a/sys/conf/files +++ b/sys/conf/files @@ -5147,6 +5147,7 @@ security/mac_bsdextended/ugidfw_system.c optional mac_bsdextended security/mac_bsdextended/ugidfw_vnode.c optional mac_bsdextended security/mac_ifoff/mac_ifoff.c optional mac_ifoff +security/mac_ipacl/mac_ipacl.c optional mac_ipacl security/mac_lomac/mac_lomac.c optional mac_lomac security/mac_mls/mac_mls.c optional mac_mls security/mac_none/mac_none.c optional mac_none diff --git a/sys/conf/options b/sys/conf/options --- a/sys/conf/options +++ b/sys/conf/options @@ -158,6 +158,7 @@ MAC_BSDEXTENDED opt_dontuse.h MAC_DDB opt_dontuse.h MAC_IFOFF opt_dontuse.h +MAC_IPACL opt_dontuse.h MAC_LOMAC opt_dontuse.h MAC_MLS opt_dontuse.h MAC_NONE opt_dontuse.h diff --git a/sys/modules/Makefile b/sys/modules/Makefile --- a/sys/modules/Makefile +++ b/sys/modules/Makefile @@ -225,6 +225,7 @@ ${_mac_bsdextended} \ ${_mac_ddb} \ ${_mac_ifoff} \ + ${_mac_ipacl} \ ${_mac_lomac} \ ${_mac_mls} \ ${_mac_none} \ @@ -581,6 +582,7 @@ _mac_ddb= mac_ddb .endif _mac_ifoff= mac_ifoff +_mac_ipacl= mac_ipacl _mac_lomac= mac_lomac _mac_mls= mac_mls _mac_none= mac_none diff --git a/sys/modules/mac_ipacl/Makefile b/sys/modules/mac_ipacl/Makefile new file mode 100644 --- /dev/null +++ b/sys/modules/mac_ipacl/Makefile @@ -0,0 +1,9 @@ +# $FreeBSD$ + +.PATH: ${SRCTOP}/sys/security/mac_ipacl + +KMOD= mac_ipacl +SRCS= mac_ipacl.c +SRCS+= opt_inet.h opt_inet6.h + +.include diff --git a/sys/netinet/in.c b/sys/netinet/in.c --- a/sys/netinet/in.c +++ b/sys/netinet/in.c @@ -77,6 +77,10 @@ #include #include +#ifdef MAC +#include +#endif + static int in_aifaddr_ioctl(u_long, caddr_t, struct ifnet *, struct ucred *); static int in_difaddr_ioctl(u_long, caddr_t, struct ifnet *, struct ucred *); static int in_gifaddr_ioctl(u_long, caddr_t, struct ifnet *, struct ucred *); @@ -487,6 +491,13 @@ if (vhid != 0 && carp_attach_p == NULL) return (EPROTONOSUPPORT); +#ifdef MAC + /* Check if a MAC policy disallows setting the IPv4 address. */ + error = mac_inet_check_add_addr(cred, &addr->sin_addr, ifp); + if (error != 0) + return (error); +#endif + /* * See whether address already exist. */ diff --git a/sys/netinet6/in6.c b/sys/netinet6/in6.c --- a/sys/netinet6/in6.c +++ b/sys/netinet6/in6.c @@ -115,6 +115,10 @@ #include #include +#ifdef MAC +#include +#endif + /* * struct in6_ifreq and struct ifreq must be type punnable for common members * of ifr_ifru to allow accessors to be shared. @@ -567,6 +571,12 @@ break; case SIOCAIFADDR_IN6: +#ifdef MAC + /* Check if a MAC policy disallows setting the IPv6 address. */ + error = mac_inet6_check_add_addr(cred, &sa6->sin6_addr, ifp); + if (error != 0) + goto out; +#endif error = in6_addifaddr(ifp, ifra, ia); ia = NULL; break; diff --git a/sys/security/mac/mac_framework.h b/sys/security/mac/mac_framework.h --- a/sys/security/mac/mac_framework.h +++ b/sys/security/mac/mac_framework.h @@ -90,6 +90,9 @@ struct vnode; struct vop_setlabel_args; +struct in_addr; +struct in6_addr; + #include /* XXX acl_type_t */ #include /* accmode_t */ @@ -191,6 +194,12 @@ int mac_ifnet_ioctl_set(struct ucred *cred, struct ifreq *ifr, struct ifnet *ifp); +/* Check if the IP address is allowed for the interface. */ +int mac_inet_check_add_addr(struct ucred *cred, + const struct in_addr *ia, struct ifnet *ifp); +int mac_inet6_check_add_addr(struct ucred *cred, + const struct in6_addr *ia6, struct ifnet *ifp); + int mac_inpcb_check_deliver(struct inpcb *inp, struct mbuf *m); int mac_inpcb_check_visible(struct ucred *cred, struct inpcb *inp); void mac_inpcb_create(struct socket *so, struct inpcb *inp); diff --git a/sys/security/mac/mac_inet.c b/sys/security/mac/mac_inet.c --- a/sys/security/mac/mac_inet.c +++ b/sys/security/mac/mac_inet.c @@ -108,6 +108,17 @@ return (0); } +/* Check with rules in module if the IPv4 address is allowed. */ +int +mac_inet_check_add_addr(struct ucred *cred, const struct in_addr *ia, + struct ifnet *ifp) +{ + int error; + + MAC_POLICY_CHECK(ip4_check_jail, cred, ia, ifp); + return (error); +} + static struct label * mac_ipq_label_alloc(int flag) { diff --git a/sys/security/mac/mac_inet6.c b/sys/security/mac/mac_inet6.c --- a/sys/security/mac/mac_inet6.c +++ b/sys/security/mac/mac_inet6.c @@ -173,6 +173,17 @@ q6->ip6q_label); } +/* Check with rules in module if the IPv6 address is allowed. */ +int +mac_inet6_check_add_addr(struct ucred *cred, const struct in6_addr *ia6, + struct ifnet *ifp) +{ + int error; + + MAC_POLICY_CHECK(ip6_check_jail, cred, ia6, ifp); + return (error); +} + void mac_netinet6_nd6_send(struct ifnet *ifp, struct mbuf *m) { diff --git a/sys/security/mac/mac_policy.h b/sys/security/mac/mac_policy.h --- a/sys/security/mac/mac_policy.h +++ b/sys/security/mac/mac_policy.h @@ -104,6 +104,9 @@ struct vattr; struct vnode; +struct in_addr; +struct in6_addr; + /* * Policy module operations. */ @@ -248,6 +251,12 @@ typedef void (*mpo_ip6q_update_t)(struct mbuf *m, struct label *mlabel, struct ip6q *q6, struct label *q6label); +/* Policy ops checking IPv4 and IPv6 address for ipacl. */ +typedef int (*mpo_ip4_check_jail_t)(struct ucred *cred, + const struct in_addr *ia, struct ifnet *ifp); +typedef int (*mpo_ip6_check_jail_t)(struct ucred *cred, + const struct in6_addr *ia6, struct ifnet *ifp); + typedef void (*mpo_ipq_create_t)(struct mbuf *m, struct label *mlabel, struct ipq *q, struct label *qlabel); typedef void (*mpo_ipq_destroy_label_t)(struct label *label); @@ -762,6 +771,9 @@ mpo_inpcb_init_label_t mpo_inpcb_init_label; mpo_inpcb_sosetlabel_t mpo_inpcb_sosetlabel; + mpo_ip4_check_jail_t mpo_ip4_check_jail; + mpo_ip6_check_jail_t mpo_ip6_check_jail; + mpo_ip6q_create_t mpo_ip6q_create; mpo_ip6q_destroy_label_t mpo_ip6q_destroy_label; mpo_ip6q_init_label_t mpo_ip6q_init_label; diff --git a/sys/security/mac_ipacl/mac_ipacl.c b/sys/security/mac_ipacl/mac_ipacl.c new file mode 100644 --- /dev/null +++ b/sys/security/mac_ipacl/mac_ipacl.c @@ -0,0 +1,453 @@ +/*- + * Copyright (c) 2003-2004 Networks Associates Technology, Inc. + * Copyright (c) 2006 SPARTA, Inc. + * Copyright (c) 2019, 2023 Shivank Garg + * + * This software was developed for the FreeBSD Project by Network + * Associates Laboratories, the Security Research Division of Network + * Associates, Inc. under DARPA/SPAWAR contract N66001-01-C-8035 ("CBOSS"), + * as part of the DARPA CHATS research program. + * + * This software was enhanced by SPARTA ISSO under SPAWAR contract + * N66001-04-C-6019 ("SEFOS"). + * + * This code was developed as a Google Summer of Code 2019 project + * under the guidance of Bjoern A. Zeeb. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +/* + * The IP address access control policy module - mac_ipacl allows the root of + * the host to limit the VNET jail's privileges of setting IPv4 and IPv6 + * addresses via sysctl(8) interface. So, the host can define rules for jails + * and their interfaces about IP addresses. + * sysctl(8) is to be used to modify the rules string in following format- + * "jail_id,allow,interface,address_family,IP_addr/prefix_length[@jail_id,...]" + */ + +#include "opt_inet.h" +#include "opt_inet6.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include + +SYSCTL_DECL(_security_mac); + +static SYSCTL_NODE(_security_mac, OID_AUTO, ipacl, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, + "TrustedBSD mac_ipacl policy controls"); + +#ifdef INET +static int ipacl_ipv4 = 1; +SYSCTL_INT(_security_mac_ipacl, OID_AUTO, ipv4, CTLFLAG_RWTUN, + &ipacl_ipv4, 0, "Enforce mac_ipacl for IPv4 addresses"); +#endif + +#ifdef INET6 +static int ipacl_ipv6 = 1; +SYSCTL_INT(_security_mac_ipacl, OID_AUTO, ipv6, CTLFLAG_RWTUN, + &ipacl_ipv6, 0, "Enforce mac_ipacl for IPv6 addresses"); +#endif + +static MALLOC_DEFINE(M_IPACL, "ipacl_rule", "Rules for mac_ipacl"); + +#define MAC_RULE_STRING_LEN 1024 + +struct ipacl_addr { + union { +#ifdef INET + struct in_addr ipv4; +#endif +#ifdef INET6 + struct in6_addr ipv6; +#endif + u_int8_t addr8[16]; + u_int16_t addr16[8]; + u_int32_t addr32[4]; + } ipa; /* 128 bit address*/ +#ifdef INET +#define v4 ipa.ipv4 +#endif +#ifdef INET6 +#define v6 ipa.ipv6 +#endif +#define addr8 ipa.addr8 +#define addr16 ipa.addr16 +#define addr32 ipa.addr32 +}; + +struct ip_rule { + int jid; + bool allow; + bool subnet_apply; /* Apply rule on whole subnet. */ + char if_name[IFNAMSIZ]; + int af; /* Address family. */ + struct ipacl_addr addr; + struct ipacl_addr mask; + TAILQ_ENTRY(ip_rule) r_entries; +}; + +static struct mtx rule_mtx; +static TAILQ_HEAD(rulehead, ip_rule) rule_head; +static char rule_string[MAC_RULE_STRING_LEN]; + +static void +destroy_rules(struct rulehead *head) +{ + struct ip_rule *rule; + + while ((rule = TAILQ_FIRST(head)) != NULL) { + TAILQ_REMOVE(head, rule, r_entries); + free(rule, M_IPACL); + } +} + +static void +ipacl_init(struct mac_policy_conf *conf) +{ + mtx_init(&rule_mtx, "rule_mtx", NULL, MTX_DEF); + TAILQ_INIT(&rule_head); +} + +static void +ipacl_destroy(struct mac_policy_conf *conf) +{ + mtx_destroy(&rule_mtx); + destroy_rules(&rule_head); +} + +/* + * Note: parsing routines are destructive on the passed string. + */ +static int +parse_rule_element(char *element, struct ip_rule *rule) +{ + char *tok, *p; + int prefix; +#ifdef INET6 + int i; +#endif + + /* Should we support a jail wildcard? */ + tok = strsep(&element, ","); + if (tok == NULL) + return (EINVAL); + rule->jid = strtol(tok, &p, 10); + if (*p != '\0') + return (EINVAL); + tok = strsep(&element, ","); + if (tok == NULL) + return (EINVAL); + rule->allow = strtol(tok, &p, 10); + if (*p != '\0') + return (EINVAL); + tok = strsep(&element, ","); + if (strlen(tok) + 1 > IFNAMSIZ) + return (EINVAL); + /* Empty interface name is wildcard to all interfaces. */ + strlcpy(rule->if_name, tok, strlen(tok) + 1); + tok = strsep(&element, ","); + if (tok == NULL) + return (EINVAL); + rule->af = (strcmp(tok, "AF_INET") == 0) ? AF_INET : + (strcmp(tok, "AF_INET6") == 0) ? AF_INET6 : -1; + if (rule->af == -1) + return (EINVAL); + tok = strsep(&element, "/"); + if (tok == NULL) + return (EINVAL); + if (inet_pton(rule->af, tok, rule->addr.addr32) != 1) + return (EINVAL); + tok = element; + if (tok == NULL) + return (EINVAL); + prefix = strtol(tok, &p, 10); + if (*p != '\0') + return (EINVAL); + /* Value -1 for prefix make policy applicable to individual IP only. */ + if (prefix == -1) + rule->subnet_apply = false; + else { + rule->subnet_apply = true; + switch (rule->af) { +#ifdef INET + case AF_INET: + if (prefix < 0 || prefix > 32) + return (EINVAL); + + if (prefix == 0) + rule->mask.addr32[0] = htonl(0); + else + rule->mask.addr32[0] = + htonl(~((1 << (32 - prefix)) - 1)); + rule->addr.addr32[0] &= rule->mask.addr32[0]; + break; +#endif +#ifdef INET6 + case AF_INET6: + if (prefix < 0 || prefix > 128) + return (EINVAL); + + for (i = 0; prefix > 0; prefix -= 8, i++) + rule->mask.addr8[i] = prefix >= 8 ? 0xFF : + (u_int8_t)((0xFFU << (8 - prefix)) & 0xFFU); + for (i = 0; i < 16; i++) + rule->addr.addr8[i] &= rule->mask.addr8[i]; + break; +#endif + } + } + return (0); +} + +/* + * Format of Rule- jid,allow,interface_name,addr_family,ip_addr/subnet_mask + * Example: sysctl security.mac.ipacl.rules=1,1,epair0b,AF_INET,192.0.2.2/24 + */ +static int +parse_rules(char *string, struct rulehead *head) +{ + struct ip_rule *new; + char *element; + int error; + + error = 0; + while ((element = strsep(&string, "@")) != NULL) { + if (strlen(element) == 0) + continue; + + new = malloc(sizeof(*new), M_IPACL, M_ZERO | M_WAITOK); + error = parse_rule_element(element, new); + if (error != 0) { + free(new, M_IPACL); + goto out; + } + TAILQ_INSERT_TAIL(head, new, r_entries); + } +out: + if (error != 0) + destroy_rules(head); + return (error); +} + +static int +sysctl_rules(SYSCTL_HANDLER_ARGS) +{ + char *string, *copy_string, *new_string; + struct rulehead head, save_head; + int error; + + new_string = NULL; + if (req->newptr != NULL) { + new_string = malloc(MAC_RULE_STRING_LEN, M_IPACL, + M_WAITOK | M_ZERO); + mtx_lock(&rule_mtx); + strcpy(new_string, rule_string); + mtx_unlock(&rule_mtx); + string = new_string; + } else + string = rule_string; + + error = sysctl_handle_string(oidp, string, MAC_RULE_STRING_LEN, req); + if (error) + goto out; + + if (req->newptr != NULL) { + copy_string = strdup(string, M_IPACL); + TAILQ_INIT(&head); + error = parse_rules(copy_string, &head); + free(copy_string, M_IPACL); + if (error) + goto out; + + TAILQ_INIT(&save_head); + mtx_lock(&rule_mtx); + TAILQ_CONCAT(&save_head, &rule_head, r_entries); + TAILQ_CONCAT(&rule_head, &head, r_entries); + strcpy(rule_string, string); + mtx_unlock(&rule_mtx); + destroy_rules(&save_head); + } +out: + if (new_string != NULL) + free(new_string, M_IPACL); + return (error); +} +SYSCTL_PROC(_security_mac_ipacl, OID_AUTO, rules, + CTLTYPE_STRING | CTLFLAG_RW | CTLFLAG_MPSAFE, 0, + 0, sysctl_rules, "A", "IP ACL Rules"); + +static int +rules_check(struct ucred *cred, + struct ipacl_addr *ip_addr, struct ifnet *ifp) +{ + struct ip_rule *rule; + int error; +#ifdef INET6 + int i; + bool same_subnet; +#endif + + error = EPERM; + + mtx_lock(&rule_mtx); + + /* + * In the case where multiple rules are applicable to an IP address or + * a set of IP addresses, the rule that is defined later in the list + * determines the outcome, disregarding any previous rule for that IP + * address. + * Walk the policy rules list in reverse order until rule applicable + * to the requested IP address is found. + */ + TAILQ_FOREACH_REVERSE(rule, &rule_head, rulehead, r_entries) { + /* Skip if current rule applies to different jail. */ + if (cred->cr_prison->pr_id != rule->jid) + continue; + + if (strcmp(rule->if_name, "\0") && + strcmp(rule->if_name, ifp->if_xname)) + continue; + + switch (rule->af) { +#ifdef INET + case AF_INET: + if (rule->subnet_apply) { + if (rule->addr.v4.s_addr != + (ip_addr->v4.s_addr & rule->mask.v4.s_addr)) + continue; + } else + if (ip_addr->v4.s_addr != rule->addr.v4.s_addr) + continue; + break; +#endif +#ifdef INET6 + case AF_INET6: + if (rule->subnet_apply) { + same_subnet = true; + for (i = 0; i < 16; i++) + if (rule->addr.v6.s6_addr[i] != + (ip_addr->v6.s6_addr[i] & + rule->mask.v6.s6_addr[i])) { + same_subnet = false; + break; + } + if (!same_subnet) + continue; + } else + if (bcmp(&rule->addr, ip_addr, + sizeof(*ip_addr))) + continue; + break; +#endif + } + + if (rule->allow) + error = 0; + break; + } + + mtx_unlock(&rule_mtx); + + return (error); +} + +/* + * Feature request: Can we make this sysctl policy apply to jails by default, + * but also allow it to be changed to apply to the base system? + */ +#ifdef INET +static int +ipacl_ip4_check_jail(struct ucred *cred, + const struct in_addr *ia, struct ifnet *ifp) +{ + struct ipacl_addr ip4_addr; + + ip4_addr.v4 = *ia; + + if (!jailed(cred)) + return (0); + + /* Checks with the policy only when it is enforced for ipv4. */ + if (ipacl_ipv4) + return rules_check(cred, &ip4_addr, ifp); + + return (0); +} +#endif + +#ifdef INET6 +static int +ipacl_ip6_check_jail(struct ucred *cred, + const struct in6_addr *ia6, struct ifnet *ifp) +{ + struct ipacl_addr ip6_addr; + + ip6_addr.v6 = *ia6; /* Make copy to not alter the original. */ + in6_clearscope(&ip6_addr.v6); /* Clear the scope id. */ + + if (!jailed(cred)) + return (0); + + /* Checks with the policy when it is enforced for ipv6. */ + if (ipacl_ipv6) + return rules_check(cred, &ip6_addr, ifp); + + return (0); +} +#endif + +static struct mac_policy_ops ipacl_ops = +{ + .mpo_init = ipacl_init, + .mpo_destroy = ipacl_destroy, +#ifdef INET + .mpo_ip4_check_jail = ipacl_ip4_check_jail, +#endif +#ifdef INET6 + .mpo_ip6_check_jail = ipacl_ip6_check_jail, +#endif +}; + +MAC_POLICY_SET(&ipacl_ops, mac_ipacl, "TrustedBSD MAC/ipacl", + MPC_LOADTIME_FLAG_UNLOADOK, NULL); diff --git a/tests/sys/mac/Makefile b/tests/sys/mac/Makefile --- a/tests/sys/mac/Makefile +++ b/tests/sys/mac/Makefile @@ -3,6 +3,7 @@ TESTSDIR= ${TESTSBASE}/sys/mac TESTS_SUBDIRS+= bsdextended +TESTS_SUBDIRS+= ipacl TESTS_SUBDIRS+= portacl .include diff --git a/tests/sys/mac/ipacl/Makefile b/tests/sys/mac/ipacl/Makefile new file mode 100644 --- /dev/null +++ b/tests/sys/mac/ipacl/Makefile @@ -0,0 +1,11 @@ +# $FreeBSD$ + +PACKAGE= tests + +TESTSDIR= ${TESTSBASE}/sys/mac/ipacl + +ATF_TESTS_SH+= ipacl_test + +${PACKAGE}FILES+= utils.subr + +.include diff --git a/tests/sys/mac/ipacl/ipacl_test.sh b/tests/sys/mac/ipacl/ipacl_test.sh new file mode 100644 --- /dev/null +++ b/tests/sys/mac/ipacl/ipacl_test.sh @@ -0,0 +1,282 @@ +#- +# Copyright (c) 2019, 2023 Shivank Garg +# +# This code was developed as a Google Summer of Code 2019 project +# under the guidance of Bjoern A. Zeeb. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. +# +# $FreeBSD$ + +. $(atf_get_srcdir)/utils.subr + +atf_test_case "ipacl_v4" "cleanup" + +ipacl_v4_head() +{ + atf_set descr 'basic test for ipacl on IPv4 addresses' + atf_set require.user root +} + +ipacl_v4_body() +{ + ipacl_test_init + + epairA=$(vnet_mkepair) + epairB=$(vnet_mkepair) + epairC=$(vnet_mkepair) + + vnet_mkjail A ${epairA}b + vnet_mkjail B ${epairB}b ${epairC}b + + jidA=$(jls -j A -s jid | grep -o -E '[0-9]+') + jidB=$(jls -j B -s jid | grep -o -E '[0-9]+') + + # The ipacl policy module is not enforced for IPv4. + sysctl security.mac.ipacl.ipv4=0 + + atf_check -s exit:0 -e ignore \ + jexec A ifconfig ${epairA}b 192.0.2.2/24 up + atf_check -s exit:0 -e ignore \ + jexec A ifconfig ${epairA}b 203.0.113.254/24 up + + # The ipacl policy module is enforced for IPv4 and prevent all + # jails from setting their IPv4 address. + sysctl security.mac.ipacl.ipv4=1 + sysctl security.mac.ipacl.rules= + + atf_check -s not-exit:0 -e ignore \ + jexec A ifconfig ${epairA}b 192.0.2.2/24 up + atf_check -s not-exit:0 -e ignore \ + jexec A ifconfig ${epairA}b 203.0.113.254/24 up + + rule="${jidA},1,${epairA}b,AF_INET,192.0.2.42/-1@" + rule="${rule}${jidB},1,${epairB}b,AF_INET,198.51.100.12/-1@" + rule="${rule}${jidB},1,,AF_INET,203.0.113.1/24@" + rule="${rule}${jidB},0,,AF_INET,203.0.113.9/-1" + sysctl security.mac.ipacl.rules="${rule}" + + # Verify if it allows jail to set only certain IPv4 address. + atf_check -s exit:0 -e ignore \ + jexec A ifconfig ${epairA}b 192.0.2.42/24 up + atf_check -s not-exit:0 -e ignore \ + jexec A ifconfig ${epairA}b 192.0.2.43/24 up + atf_check -s exit:0 -e ignore \ + jexec B ifconfig ${epairB}b 198.51.100.12/24 up + atf_check -s not-exit:0 -e ignore \ + jexec B ifconfig ${epairC}b 198.51.100.12/24 up + + # Verify if the module allow jail to set any address in subnet. + atf_check -s exit:0 -e ignore \ + jexec B ifconfig ${epairB}b 203.0.113.19/24 up + atf_check -s exit:0 -e ignore \ + jexec B ifconfig ${epairB}b 203.0.113.241/24 up + atf_check -s not-exit:0 -e ignore \ + jexec B ifconfig ${epairB}b 198.18.0.1/24 up + atf_check -s not-exit:0 -e ignore \ + jexec B ifconfig ${epairB}b 203.0.113.9/24 up + + # Check wildcard for interfaces. + atf_check -s exit:0 -e ignore \ + jexec B ifconfig ${epairC}b 203.0.113.20/24 up + atf_check -s exit:0 -e ignore \ + jexec B ifconfig ${epairC}b 203.0.113.242/24 up + atf_check -s not-exit:0 -e ignore \ + jexec B ifconfig ${epairC}b 198.18.0.1/24 up + atf_check -s not-exit:0 -e ignore \ + jexec B ifconfig ${epairC}b 203.0.113.9/24 up + + rule="${jidA},1,,AF_INET,198.18.0.0/15@" + rule="${rule}${jidA},0,,AF_INET,198.18.23.0/24@" + rule="${rule}${jidA},1,,AF_INET,198.18.23.1/-1@" + rule="${rule}${jidA},1,,AF_INET,198.51.100.0/24@" + rule="${rule}${jidA},0,,AF_INET,198.51.100.100/-1" + sysctl security.mac.ipacl.rules="${rule}" + + # Tests from Benchamarking and Documentation(TEST-NET-3). + atf_check -s exit:0 -e ignore \ + jexec A ifconfig ${epairA}b 198.18.0.1/24 up + atf_check -s not-exit:0 -e ignore \ + jexec A ifconfig ${epairA}b 198.18.23.2/24 up + atf_check -s exit:0 -e ignore \ + jexec A ifconfig ${epairA}b 198.18.23.1/22 up + atf_check -s not-exit:0 -e ignore \ + jexec A ifconfig ${epairA}b 198.18.23.3/24 up + + atf_check -s exit:0 -e ignore \ + jexec A ifconfig ${epairA}b 198.51.100.001/24 up + atf_check -s exit:0 -e ignore \ + jexec A ifconfig ${epairA}b 198.51.100.254/24 up + atf_check -s not-exit:0 -e ignore \ + jexec A ifconfig ${epairA}b 198.51.100.100/24 up + atf_check -s not-exit:0 -e ignore \ + jexec A ifconfig ${epairA}b 203.0.113.1/24 up + + # Reset rules OID. + sysctl security.mac.ipacl.rules= +} + +ipacl_v4_cleanup() +{ + ipacl_test_cleanup +} + +atf_test_case "ipacl_v6" "cleanup" + +ipacl_v6_head() +{ + atf_set descr 'basic test for ipacl on IPv6 addresses' + atf_set require.user root +} + +ipacl_v6_body() +{ + ipacl_test_init + + epairA=$(vnet_mkepair) + epairB=$(vnet_mkepair) + epairC=$(vnet_mkepair) + + vnet_mkjail A ${epairA}b + vnet_mkjail B ${epairB}b ${epairC}b + + jidA=$(jls -j A -s jid | grep -o -E '[0-9]+') + jidB=$(jls -j B -s jid | grep -o -E '[0-9]+') + + # The ipacl policy module is not enforced for IPv6. + sysctl security.mac.ipacl.ipv6=0 + + atf_check -s exit:0 -e ignore \ + jexec A ifconfig ${epairA}b inet6 2001:2::abcd/48 up + atf_check -s exit:0 -e ignore \ + jexec A ifconfig ${epairA}b inet6 2001:2::5ea:11/48 up + + # The ipacl policy module is enforced for IPv6 and prevent all + # jails from setting their IPv6 address. + sysctl security.mac.ipacl.ipv6=1 + sysctl security.mac.ipacl.rules= + + atf_check -s not-exit:0 -e ignore \ + jexec A ifconfig ${epairA}b inet6 2001:2::abcd/48 up + atf_check -s not-exit:0 -e ignore \ + jexec A ifconfig ${epairA}b inet6 2001:2::5ea:11/48 up + + rule="${jidA},1,${epairA}b,AF_INET6,2001:db8::1111/-1@" + rule="${rule}${jidB},1,${epairB}b,AF_INET6,2001:2::1234:1234/-1@" + rule="${rule}${jidB},1,,AF_INET6,fe80::/32@" + rule="${rule}${jidB},0,,AF_INET6,fe80::abcd/-1" + sysctl security.mac.ipacl.rules="${rule}" + + # Verify if it allows jail to set only certain IPv6 address. + atf_check -s exit:0 -e ignore \ + jexec A ifconfig ${epairA}b inet6 2001:db8::1111/64 up + atf_check -s not-exit:0 -e ignore \ + jexec A ifconfig ${epairA}b inet6 2001:db8::1112/64 up + atf_check -s exit:0 -e ignore \ + jexec B ifconfig ${epairB}b inet6 2001:2::1234:1234/48 up + atf_check -s not-exit:0 -e ignore \ + jexec A ifconfig ${epairA}b inet6 2001:2::1234:1234/48 up + + # Verify if the module allow jail set any address in subnet. + atf_check -s exit:0 -e ignore \ + jexec B ifconfig ${epairB}b inet6 FE80::1101:1221/15 up + atf_check -s exit:0 -e ignore \ + jexec B ifconfig ${epairB}b inet6 FE80::abab/15 up + atf_check -s exit:0 -e ignore \ + jexec B ifconfig ${epairB}b inet6 FE80::1/64 up + atf_check -s not-exit:0 -e ignore \ + jexec B ifconfig ${epairB}b inet6 FE80::abcd/15 up + + # Check wildcard for interfaces. + atf_check -s exit:0 -e ignore \ + jexec B ifconfig ${epairC}b inet6 FE80::1101:1221/15 up + atf_check -s exit:0 -e ignore \ + jexec B ifconfig ${epairC}b inet6 FE80::abab/32 up + atf_check -s not-exit:0 -e ignore \ + jexec B ifconfig ${epairC}b inet6 FE81::1/64 up + atf_check -s not-exit:0 -e ignore \ + jexec B ifconfig ${epairC}b inet6 FE80::abcd/32 up + + rule="${jidB},1,,AF_INET6,2001:2::/48@" + rule="${rule}${jidB},1,,AF_INET6,2001:3::/32" + sysctl security.mac.ipacl.rules="${rule}" + + # Tests when subnet is allowed. + atf_check -s not-exit:0 -e ignore \ + jexec B ifconfig ${epairC}b inet6 2001:2:0001::1/64 up + atf_check -s not-exit:0 -e ignore \ + jexec B ifconfig ${epairC}b inet6 2001:2:1000::1/32 up + atf_check -s exit:0 -e ignore \ + jexec B ifconfig ${epairC}b inet6 2001:3:0001::1/64 up + atf_check -s not-exit:0 -e ignore \ + jexec B ifconfig ${epairC}b inet6 2001:4::1/64 up + + # More tests of ULA address space. + rule="${jidA},1,,AF_INET6,fc00::/7@" + rule="${rule}${jidA},0,,AF_INET6,fc00::1111:2200/120@" + rule="${rule}${jidA},1,,AF_INET6,fc00::1111:2299/-1@" + rule="${rule}${jidA},1,,AF_INET6,2001:db8::/32@" + rule="${rule}${jidA},0,,AF_INET6,2001:db8::abcd/-1" + sysctl security.mac.ipacl.rules="${rule}" + + atf_check -s exit:0 -e ignore \ + jexec A ifconfig ${epairA}b inet6 fc00::0000:1234/48 up + atf_check -s exit:0 -e ignore \ + jexec A ifconfig ${epairA}b inet6 fc00::0000:1234/48 up + atf_check -s not-exit:0 -e ignore \ + jexec A ifconfig ${epairA}b inet6 f800::2222:2200/48 up + atf_check -s not-exit:0 -e ignore \ + jexec A ifconfig ${epairA}b inet6 f800::2222:22ff/48 up + + atf_check -s exit:0 -e ignore \ + jexec A ifconfig ${epairA}b inet6 fc00::1111:2111/64 up + atf_check -s not-exit:0 -e ignore \ + jexec A ifconfig ${epairA}b inet6 fc00::1111:2211/64 up + atf_check -s not-exit:0 -e ignore \ + jexec A ifconfig ${epairA}b inet6 fc00::1111:22aa/48 up + atf_check -s exit:0 -e ignore \ + jexec A ifconfig ${epairA}b inet6 fc00::1111:2299/48 up + + # More tests from IPv6 documentation range. + atf_check -s exit:0 -e ignore jexec A ifconfig \ + ${epairA}b inet6 2001:db8:abcd:bcde:cdef:def1:ef12:f123/32 up + atf_check -s exit:0 -e ignore jexec A ifconfig \ + ${epairA}b inet6 2001:db8:1111:2222:3333:4444:5555:6666/32 up + atf_check -s not-exit:0 -e ignore jexec A ifconfig \ + ${epairA}b inet6 2001:ab9:1111:2222:3333:4444:5555:6666/32 up + atf_check -s not-exit:0 -e ignore jexec A ifconfig \ + ${epairA}b inet6 2001:db8::abcd/32 up + + # Reset rules OID. + sysctl security.mac.ipacl.rules= +} + +ipacl_v6_cleanup() +{ + ipacl_test_cleanup +} + +atf_init_test_cases() +{ + atf_add_test_case "ipacl_v4" + atf_add_test_case "ipacl_v6" +} diff --git a/tests/sys/mac/ipacl/utils.subr b/tests/sys/mac/ipacl/utils.subr new file mode 100644 --- /dev/null +++ b/tests/sys/mac/ipacl/utils.subr @@ -0,0 +1,18 @@ +# $FreeBSD$ +# Utility functions for mac_ipacl tests + +. $(atf_get_srcdir)/../../common/vnet.subr + +ipacl_test_init() +{ + vnet_init + + if ! kldstat -q -m mac_ipacl; then + atf_skip "mac_ipacl is not loaded" + fi +} + +ipacl_test_cleanup() +{ + vnet_cleanup +}