diff mbox series

[2/2] checkpolicy: support CIDR notation for nodecon statements

Message ID 20240508170422.1396740-2-cgoettsche@seltendoof.de (mailing list archive)
State New
Headers show
Series [1/2] checkpolicy: perform contiguous check in host byte order | expand

Commit Message

Christian Göttsche May 8, 2024, 5:04 p.m. UTC
From: Christian Göttsche <cgzones@googlemail.com>

Support the Classless Inter-Domain Routing (CIDR) notation for IP
addresses with their associated network masks in nodecon statements.
The two following statements are equivalent:

    nodecon 10.8.0.0 255.255.0.0 USER1:ROLE1:TYPE1
    nodecon 10.8.0.0/16          USER1:ROLE1:TYPE1

Signed-off-by: Christian Göttsche <cgzones@googlemail.com>
---
 checkpolicy/policy_define.c                   | 207 ++++++++++++++++++
 checkpolicy/policy_define.h                   |   2 +
 checkpolicy/policy_parse.y                    |  12 +
 checkpolicy/policy_scan.l                     |   2 +
 checkpolicy/tests/policy_allonce.conf         |   2 +
 .../tests/policy_allonce.expected.conf        |   2 +
 .../tests/policy_allonce.expected_opt.conf    |   2 +
 7 files changed, 229 insertions(+)
diff mbox series

Patch

diff --git a/checkpolicy/policy_define.c b/checkpolicy/policy_define.c
index 9671906f..1d17f73d 100644
--- a/checkpolicy/policy_define.c
+++ b/checkpolicy/policy_define.c
@@ -5335,6 +5335,100 @@  out:
 	return rc;
 }
 
+int define_ipv4_cidr_node_context(void)
+{
+	char *endptr, *id, *split;
+	unsigned long mask_bits;
+	uint32_t mask;
+	struct in_addr addr;
+	ocontext_t *newc, *c, *l, *head;
+	int rc;
+
+	if (policydbp->target_platform != SEPOL_TARGET_SELINUX) {
+		yyerror("nodecon not supported for target");
+		return -1;
+	}
+
+	if (pass == 1) {
+		free(queue_remove(id_queue));
+		parse_security_context(NULL);
+		return 0;
+	}
+
+	id = queue_remove(id_queue);
+	if (!id) {
+		yyerror("failed to read IPv4 address");
+		return -1;
+	}
+
+	split = strchr(id, '/');
+	if (!split) {
+		yyerror2("invalid IPv4 CIDR notation: %s", id);
+		free(id);
+		return -1;
+	}
+	*split = '\0';
+
+	rc = inet_pton(AF_INET, id, &addr);
+	if (rc < 1) {
+		yyerror2("failed to parse IPv4 address %s", id);
+		free(id);
+		return -1;
+	}
+
+	errno = 0;
+	mask_bits = strtoul(split + 1, &endptr, 10);
+	if (errno || *endptr != '\0' || mask_bits > 32) {
+		yyerror2("invalid mask in IPv4 CIDR notation: %s", split + 1);
+		free(id);
+		return -1;
+	}
+
+	free(id);
+
+	if (mask_bits == 0) {
+		yywarn("IPv4 CIDR mask of 0, matching all IPs");
+		mask = 0;
+	} else {
+		mask = ~((UINT32_C(1) << (32 - mask_bits)) - 1);
+		mask = htobe32(mask);
+	}
+
+	if ((~mask & addr.s_addr) != 0)
+		yywarn("host bits in IPv4 address set");
+
+	newc = calloc(1, sizeof(ocontext_t));
+	if (!newc) {
+		yyerror("out of memory");
+		return -1;
+	}
+
+	newc->u.node.addr = addr.s_addr & mask;
+	newc->u.node.mask = mask;
+
+	if (parse_security_context(&newc->context[0])) {
+		free(newc);
+		return -1;
+	}
+
+	/* Create order of most specific to least retaining
+	   the order specified in the configuration. */
+	head = policydbp->ocontexts[OCON_NODE];
+	for (l = NULL, c = head; c; l = c, c = c->next) {
+		if (newc->u.node.mask > c->u.node.mask)
+			break;
+	}
+
+	newc->next = c;
+
+	if (l)
+		l->next = newc;
+	else
+		policydbp->ocontexts[OCON_NODE] = newc;
+
+	return 0;
+}
+
 static int ipv6_is_mask_contiguous(const struct in6_addr *mask)
 {
 	int filled = 1;
@@ -5369,6 +5463,26 @@  static int ipv6_has_host_bits_set(const struct in6_addr *addr, const struct in6_
 	return 0;
 }
 
+static void ipv6_cidr_bits_to_mask(unsigned long cidr_bits, struct in6_addr *mask)
+{
+	unsigned i;
+
+	for (i = 0; i < 4; i++) {
+		if (cidr_bits == 0) {
+			mask->s6_addr32[i] = 0;
+		} else if (cidr_bits >= 32) {
+			mask->s6_addr32[i] = ~UINT32_C(0);
+		} else {
+			mask->s6_addr32[i] = htobe32(~((UINT32_C(1) << (32 - cidr_bits)) - 1));
+		}
+
+		if (cidr_bits >= 32)
+			cidr_bits -= 32;
+		else
+			cidr_bits = 0;
+	}
+}
+
 int define_ipv6_node_context(void)
 {
 	char *id;
@@ -5469,6 +5583,99 @@  int define_ipv6_node_context(void)
 	return rc;
 }
 
+int define_ipv6_cidr_node_context(void)
+{
+	char *endptr, *id, *split;
+	unsigned long mask_bits;
+	int rc;
+	struct in6_addr addr, mask;
+	ocontext_t *newc, *c, *l, *head;
+
+	if (policydbp->target_platform != SEPOL_TARGET_SELINUX) {
+		yyerror("nodecon not supported for target");
+		return -1;
+	}
+
+	if (pass == 1) {
+		free(queue_remove(id_queue));
+		free(queue_remove(id_queue));
+		parse_security_context(NULL);
+		return 0;
+	}
+
+	id = queue_remove(id_queue);
+	if (!id) {
+		yyerror("failed to read IPv6 address");
+		return -1;
+	}
+
+	split = strchr(id, '/');
+	if (!split) {
+		yyerror2("invalid IPv6 CIDR notation: %s", id);
+		free(id);
+		return -1;
+	}
+	*split = '\0';
+
+	rc = inet_pton(AF_INET6, id, &addr);
+	if (rc < 1) {
+		yyerror2("failed to parse IPv6 address %s", id);
+		free(id);
+		return -1;
+	}
+
+	errno = 0;
+	mask_bits = strtoul(split + 1, &endptr, 10);
+	if (errno || *endptr != '\0' || mask_bits > 128) {
+		yyerror2("invalid mask in IPv6 CIDR notation: %s", split + 1);
+		free(id);
+		return -1;
+	}
+
+	if (mask_bits == 0) {
+		yywarn("IPv6 CIDR mask of 0, matching all IPs");
+	}
+
+	ipv6_cidr_bits_to_mask(mask_bits, &mask);
+
+	if (ipv6_has_host_bits_set(&addr, &mask)) {
+		yywarn("host bits in ipv6 address set");
+	}
+
+	free(id);
+
+	newc = calloc(1, sizeof(ocontext_t));
+	if (!newc) {
+		yyerror("out of memory");
+		return -1;
+	}
+
+	memcpy(&newc->u.node6.addr[0], &addr.s6_addr[0], 16);
+	memcpy(&newc->u.node6.mask[0], &mask.s6_addr[0], 16);
+
+	if (parse_security_context(&newc->context[0])) {
+		free(newc);
+		return -1;
+	}
+
+	/* Create order of most specific to least retaining
+	   the order specified in the configuration. */
+	head = policydbp->ocontexts[OCON_NODE6];
+	for (l = NULL, c = head; c; l = c, c = c->next) {
+		if (memcmp(&newc->u.node6.mask, &c->u.node6.mask, 16) > 0)
+			break;
+	}
+
+	newc->next = c;
+
+	if (l)
+		l->next = newc;
+	else
+		policydbp->ocontexts[OCON_NODE6] = newc;
+
+	return 0;
+}
+
 int define_fs_use(int behavior)
 {
 	ocontext_t *newc, *c, *head;
diff --git a/checkpolicy/policy_define.h b/checkpolicy/policy_define.h
index bcbfe4f3..ef74f616 100644
--- a/checkpolicy/policy_define.h
+++ b/checkpolicy/policy_define.h
@@ -38,7 +38,9 @@  int define_genfs_context(int has_type);
 int define_initial_sid_context(void);
 int define_initial_sid(void);
 int define_ipv4_node_context(void);
+int define_ipv4_cidr_node_context(void);
 int define_ipv6_node_context(void);
+int define_ipv6_cidr_node_context(void);
 int define_level(void);
 int define_netif_context(void);
 int define_permissive(void);
diff --git a/checkpolicy/policy_parse.y b/checkpolicy/policy_parse.y
index c57a988a..ed1786d8 100644
--- a/checkpolicy/policy_parse.y
+++ b/checkpolicy/policy_parse.y
@@ -145,7 +145,9 @@  typedef int (* require_func_t)(int pass);
 %token EQUALS
 %token NOTEQUAL
 %token IPV4_ADDR
+%token IPV4_CIDR
 %token IPV6_ADDR
+%token IPV6_CIDR
 %token MODULE VERSION_IDENTIFIER REQUIRE OPTIONAL
 %token POLICYCAP
 %token PERMISSIVE
@@ -739,8 +741,12 @@  node_contexts		: node_context_def
 			;
 node_context_def	: NODECON ipv4_addr_def ipv4_addr_def security_context_def
 			{if (define_ipv4_node_context()) YYABORT;}
+			| NODECON ipv4_cidr_def security_context_def
+			{if (define_ipv4_cidr_node_context()) YYABORT;}
 			| NODECON ipv6_addr ipv6_addr security_context_def
 			{if (define_ipv6_node_context()) YYABORT;}
+			| NODECON ipv6_cidr security_context_def
+			{if (define_ipv6_cidr_node_context()) YYABORT;}
 			;
 opt_fs_uses             : fs_uses
                         |
@@ -771,6 +777,9 @@  genfs_context_def	: GENFSCON filesystem path '-' identifier security_context_def
 ipv4_addr_def		: IPV4_ADDR
 			{ if (insert_id(yytext,0)) YYABORT; }
 			;
+ipv4_cidr_def		: IPV4_CIDR
+			{ if (insert_id(yytext,0)) YYABORT; }
+			;
 xperms		: xperm
 			{ if (insert_separator(0)) YYABORT; }
 			| nested_xperm_set
@@ -899,6 +908,9 @@  number64		: NUMBER
 ipv6_addr		: IPV6_ADDR
 			{ if (insert_id(yytext,0)) YYABORT; }
 			;
+ipv6_cidr		: IPV6_CIDR
+			{ if (insert_id(yytext,0)) YYABORT; }
+			;
 policycap_def		: POLICYCAP identifier ';'
 			{if (define_polcap()) YYABORT;}
 			;
diff --git a/checkpolicy/policy_scan.l b/checkpolicy/policy_scan.l
index e46677a8..5fb9ff37 100644
--- a/checkpolicy/policy_scan.l
+++ b/checkpolicy/policy_scan.l
@@ -292,8 +292,10 @@  GLBLUB				{ return(GLBLUB); }
 {letter}({alnum}|[_\-])*([\.]?({alnum}|[_\-]))*	{ return(IDENTIFIER); }
 {digit}+|0x{hexval}+            { return(NUMBER); }
 {alnum}*{letter}{alnum}*        { return(FILESYSTEM); }
+{digit}{1,3}(\.{digit}{1,3}){3}"/"{digit}{1,2}	{ return(IPV4_CIDR); }
 {digit}{1,3}(\.{digit}{1,3}){3}    { return(IPV4_ADDR); }
 {hexval}{0,4}":"{hexval}{0,4}":"({hexval}|[:.])*  { return(IPV6_ADDR); }
+{hexval}{0,4}":"{hexval}{0,4}":"({hexval}|[:.])*"/"{digit}{1,3}	{ return(IPV6_CIDR); }
 {digit}+(\.({alnum}|[_.])*)?    { return(VERSION_IDENTIFIER); }
 #line[ ]1[ ]\"[^\n]*\"		{ set_source_file(yytext+9); }
 #line[ ]{digit}+	        {
diff --git a/checkpolicy/tests/policy_allonce.conf b/checkpolicy/tests/policy_allonce.conf
index 54a4c811..2cfbb772 100644
--- a/checkpolicy/tests/policy_allonce.conf
+++ b/checkpolicy/tests/policy_allonce.conf
@@ -71,7 +71,9 @@  portcon tcp 80 USER1:ROLE1:TYPE1
 portcon udp 100-200 USER1:ROLE1:TYPE1
 netifcon lo USER1:ROLE1:TYPE1 USER1:ROLE1:TYPE1
 nodecon 127.0.0.1 255.255.255.255 USER1:ROLE1:TYPE1
+nodecon 127.0.0.0/24 USER1:ROLE1:TYPE1
 nodecon ::ffff:127.0.0.1 ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff USER1:ROLE1:TYPE1
+nodecon ff80::/16 USER1:ROLE1:TYPE1
 # hex numbers will be turned in decimal ones
 ibpkeycon fe80:: 0xFFFF USER1:ROLE1:TYPE1
 ibpkeycon fe80:: 0-0x10 USER1:ROLE1:TYPE1
diff --git a/checkpolicy/tests/policy_allonce.expected.conf b/checkpolicy/tests/policy_allonce.expected.conf
index aff6bfa3..26d56438 100644
--- a/checkpolicy/tests/policy_allonce.expected.conf
+++ b/checkpolicy/tests/policy_allonce.expected.conf
@@ -71,7 +71,9 @@  portcon tcp 80 USER1:ROLE1:TYPE1
 portcon udp 100-200 USER1:ROLE1:TYPE1
 netifcon lo USER1:ROLE1:TYPE1 USER1:ROLE1:TYPE1
 nodecon 127.0.0.1 255.255.255.255 USER1:ROLE1:TYPE1
+nodecon 127.0.0.0 255.255.255.0 USER1:ROLE1:TYPE1
 nodecon ::ffff:127.0.0.1 ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff USER1:ROLE1:TYPE1
+nodecon ff80:: ffff:: USER1:ROLE1:TYPE1
 ibpkeycon fe80:: 65535 USER1:ROLE1:TYPE1
 ibpkeycon fe80:: 0-16 USER1:ROLE1:TYPE1
 ibendportcon mlx4_0 2 USER1:ROLE1:TYPE1
diff --git a/checkpolicy/tests/policy_allonce.expected_opt.conf b/checkpolicy/tests/policy_allonce.expected_opt.conf
index 335486d1..769be2b3 100644
--- a/checkpolicy/tests/policy_allonce.expected_opt.conf
+++ b/checkpolicy/tests/policy_allonce.expected_opt.conf
@@ -71,7 +71,9 @@  portcon tcp 80 USER1:ROLE1:TYPE1
 portcon udp 100-200 USER1:ROLE1:TYPE1
 netifcon lo USER1:ROLE1:TYPE1 USER1:ROLE1:TYPE1
 nodecon 127.0.0.1 255.255.255.255 USER1:ROLE1:TYPE1
+nodecon 127.0.0.0 255.255.255.0 USER1:ROLE1:TYPE1
 nodecon ::ffff:127.0.0.1 ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff USER1:ROLE1:TYPE1
+nodecon ff80:: ffff:: USER1:ROLE1:TYPE1
 ibpkeycon fe80:: 65535 USER1:ROLE1:TYPE1
 ibpkeycon fe80:: 0-16 USER1:ROLE1:TYPE1
 ibendportcon mlx4_0 2 USER1:ROLE1:TYPE1