Skip site navigation (1)Skip section navigation (2)
Date:      Wed, 26 Jul 2023 00:11:41 GMT
From:      "Bjoern A. Zeeb" <bz@FreeBSD.org>
To:        src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org
Subject:   git: 215bab7924f6 - main - mac_ipacl: new MAC policy module to limit jail/vnet IP configuration
Message-ID:  <202307260011.36Q0BfRM007109@gitrepo.freebsd.org>

next in thread | raw e-mail | index | archive | help
The branch main has been updated by bz:

URL: https://cgit.FreeBSD.org/src/commit/?id=215bab7924f6c8e133a96431b3e2176d5fae1eff

commit 215bab7924f6c8e133a96431b3e2176d5fae1eff
Author:     Shivank Garg <shivank@freebsd.org>
AuthorDate: 2023-07-25 20:27:06 +0000
Commit:     Bjoern A. Zeeb <bz@FreeBSD.org>
CommitDate: 2023-07-26 00:07:57 +0000

    mac_ipacl: new MAC policy module to limit jail/vnet IP configuration
    
    The mac_ipacl policy module enables fine-grained control over IP address
    configuration within VNET jails from the base system.
    It allows the root user to define rules governing IP addresses for
    jails and their interfaces using the sysctl interface.
    
    Requested by:   multiple
    Sponsored by:   Google, Inc. (GSoC 2019)
    MFC after:      2 months
    Reviewed by:    bz, dch (both earlier versions)
    Differential Revision: https://reviews.freebsd.org/D20967
---
 etc/mtree/BSD.tests.dist           |   2 +
 share/man/man4/Makefile            |   1 +
 share/man/man4/mac.4               |   4 +-
 share/man/man4/mac_ipacl.4         | 166 ++++++++++++++
 sys/conf/NOTES                     |   1 +
 sys/conf/files                     |   1 +
 sys/conf/options                   |   1 +
 sys/modules/Makefile               |   2 +
 sys/modules/mac_ipacl/Makefile     |   9 +
 sys/netinet/in.c                   |  11 +
 sys/netinet6/in6.c                 |  10 +
 sys/security/mac/mac_framework.h   |   9 +
 sys/security/mac/mac_inet.c        |  11 +
 sys/security/mac/mac_inet6.c       |  11 +
 sys/security/mac/mac_policy.h      |  12 +
 sys/security/mac_ipacl/mac_ipacl.c | 453 +++++++++++++++++++++++++++++++++++++
 tests/sys/mac/Makefile             |   1 +
 tests/sys/mac/ipacl/Makefile       |  11 +
 tests/sys/mac/ipacl/ipacl_test.sh  | 282 +++++++++++++++++++++++
 tests/sys/mac/ipacl/utils.subr     |  18 ++
 20 files changed, 1015 insertions(+), 1 deletion(-)

diff --git a/etc/mtree/BSD.tests.dist b/etc/mtree/BSD.tests.dist
index c8e88ad5705c..9312a6c2980b 100644
--- 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
index d42f77461805..6abe9375e606 100644
--- a/share/man/man4/Makefile
+++ b/share/man/man4/Makefile
@@ -292,6 +292,7 @@ MAN=	aac.4 \
 	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
index 9f52831a5f57..f91a1f50dc7c 100644
--- 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 @@ Currently, the following MAC policy modules are shipped with
 .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 @@ man page.
 .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
index 000000000000..00f8f1f413b6
--- /dev/null
+++ b/share/man/man4/mac_ipacl.4
@@ -0,0 +1,166 @@
+.\" Copyright (c) 2019, 2023 Shivank Garg <shivank@FreeBSD.org>
+.\"
+.\" 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
index 23ec7b5c45c9..895bdd0e2129 100644
--- a/sys/conf/NOTES
+++ b/sys/conf/NOTES
@@ -1232,6 +1232,7 @@ options 	MAC_BIBA
 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
index 2c72d2ede0d3..0d5372dfedc6 100644
--- a/sys/conf/files
+++ b/sys/conf/files
@@ -5147,6 +5147,7 @@ security/mac_bsdextended/mac_bsdextended.c	optional mac_bsdextended
 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
index 6f1a488bf33f..78d712b7cdbb 100644
--- a/sys/conf/options
+++ b/sys/conf/options
@@ -158,6 +158,7 @@ MAC_BIBA	opt_dontuse.h
 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
index 20945548604f..2f9e9ec0f931 100644
--- a/sys/modules/Makefile
+++ b/sys/modules/Makefile
@@ -225,6 +225,7 @@ SUBDIR=	\
 	${_mac_bsdextended} \
 	${_mac_ddb} \
 	${_mac_ifoff} \
+	${_mac_ipacl} \
 	${_mac_lomac} \
 	${_mac_mls} \
 	${_mac_none} \
@@ -581,6 +582,7 @@ _mac_bsdextended= mac_bsdextended
 _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
index 000000000000..3ecc7564e70c
--- /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 <bsd.kmod.mk>
diff --git a/sys/netinet/in.c b/sys/netinet/in.c
index fa0d311bb563..76fd0c7a8281 100644
--- a/sys/netinet/in.c
+++ b/sys/netinet/in.c
@@ -77,6 +77,10 @@ __FBSDID("$FreeBSD$");
 #include <netinet/udp.h>
 #include <netinet/udp_var.h>
 
+#ifdef MAC
+#include <security/mac/mac_framework.h>
+#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 @@ in_aifaddr_ioctl(u_long cmd, caddr_t data, struct ifnet *ifp, struct ucred *cred
 	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
index 24a2c20b0cca..7c8806c7a2cd 100644
--- a/sys/netinet6/in6.c
+++ b/sys/netinet6/in6.c
@@ -115,6 +115,10 @@ __FBSDID("$FreeBSD$");
 #include <netinet6/in6_fib.h>
 #include <netinet6/in6_pcb.h>
 
+#ifdef MAC
+#include <security/mac/mac_framework.h>
+#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 @@ in6_control_ioctl(u_long cmd, void *data,
 		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
index 8a1de6fe13e1..6ffd8e27bcb7 100644
--- a/sys/security/mac/mac_framework.h
+++ b/sys/security/mac/mac_framework.h
@@ -90,6 +90,9 @@ struct vattr;
 struct vnode;
 struct vop_setlabel_args;
 
+struct in_addr;
+struct in6_addr;
+
 #include <sys/acl.h>			/* XXX acl_type_t */
 #include <sys/types.h>			/* accmode_t */
 
@@ -191,6 +194,12 @@ int	mac_ifnet_ioctl_get(struct ucred *cred, struct ifreq *ifr,
 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
index dd77a6825204..7dcdfd1decf4 100644
--- a/sys/security/mac/mac_inet.c
+++ b/sys/security/mac/mac_inet.c
@@ -108,6 +108,17 @@ mac_inpcb_init(struct inpcb *inp, int flag)
 	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
index cb0812bab785..16c78ee1a6bd 100644
--- a/sys/security/mac/mac_inet6.c
+++ b/sys/security/mac/mac_inet6.c
@@ -173,6 +173,17 @@ mac_ip6q_update(struct mbuf *m, struct ip6q *q6)
 	    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
index 1f9a5485b136..1a6b19b79f85 100644
--- a/sys/security/mac/mac_policy.h
+++ b/sys/security/mac/mac_policy.h
@@ -104,6 +104,9 @@ struct ucred;
 struct vattr;
 struct vnode;
 
+struct in_addr;
+struct in6_addr;
+
 /*
  * Policy module operations.
  */
@@ -248,6 +251,12 @@ typedef void	(*mpo_ip6q_reassemble)(struct ip6q *q6, struct label *q6label,
 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 @@ struct mac_policy_ops {
 	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
index 000000000000..ed207f38dbb8
--- /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 <shivank@FreeBSD.org>
+ *
+ * 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 <sys/param.h>
+#include <sys/module.h>
+#include <sys/errno.h>
+#include <sys/kernel.h>
+#include <sys/mutex.h>
+#include <sys/priv.h>
+#include <sys/queue.h>
+#include <sys/socket.h>
+#include <sys/sysctl.h>
+#include <sys/systm.h>
+#include <sys/types.h>
+#include <sys/ucred.h>
+#include <sys/jail.h>
+
+#include <net/if.h>
+#include <net/if_var.h>
+
+#include <netinet/in.h>
+#include <netinet6/scope6_var.h>
+
+#include <security/mac/mac_policy.h>
+
+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
index ae2c4917b0d1..f385b5cefb58 100644
--- 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 <bsd.test.mk>
diff --git a/tests/sys/mac/ipacl/Makefile b/tests/sys/mac/ipacl/Makefile
new file mode 100644
index 000000000000..0da5d482de64
--- /dev/null
+++ b/tests/sys/mac/ipacl/Makefile
@@ -0,0 +1,11 @@
+# $FreeBSD$
+
+PACKAGE=		tests
*** 320 LINES SKIPPED ***



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?202307260011.36Q0BfRM007109>