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
This commit is contained in:
Shivank Garg 2023-07-25 20:27:06 +00:00 committed by Bjoern A. Zeeb
parent a1b6757313
commit 215bab7924
20 changed files with 1015 additions and 1 deletions

View file

@ -815,6 +815,8 @@
mac
bsdextended
..
ipacl
..
portacl
..
..

View file

@ -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 \

View file

@ -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 ,

166
share/man/man4/mac_ipacl.4 Normal file
View file

@ -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 .

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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>

View file

@ -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.
*/

View file

@ -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;

View file

@ -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);

View file

@ -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)
{

View file

@ -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)
{

View file

@ -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;

View file

@ -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);

View file

@ -3,6 +3,7 @@
TESTSDIR= ${TESTSBASE}/sys/mac
TESTS_SUBDIRS+= bsdextended
TESTS_SUBDIRS+= ipacl
TESTS_SUBDIRS+= portacl
.include <bsd.test.mk>

View file

@ -0,0 +1,11 @@
# $FreeBSD$
PACKAGE= tests
TESTSDIR= ${TESTSBASE}/sys/mac/ipacl
ATF_TESTS_SH+= ipacl_test
${PACKAGE}FILES+= utils.subr
.include <bsd.test.mk>

View file

@ -0,0 +1,282 @@
#-
# 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$
. $(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"
}

View file

@ -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
}