From: "Christian Göttsche" <cgoettsche@seltendoof.de>
To: selinux@vger.kernel.org
Cc: "Christian Göttsche" <cgzones@googlemail.com>
Subject: [PATCH 2/2] checkpolicy: support CIDR notation for nodecon statements
Date: Wed, 8 May 2024 19:04:22 +0200 [thread overview]
Message-ID: <20240508170422.1396740-2-cgoettsche@seltendoof.de> (raw)
In-Reply-To: <20240508170422.1396740-1-cgoettsche@seltendoof.de>
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 --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
--
2.43.0
next prev parent reply other threads:[~2024-05-08 17:04 UTC|newest]
Thread overview: 4+ messages / expand[flat|nested] mbox.gz Atom feed top
2024-05-08 17:04 [PATCH 1/2] checkpolicy: perform contiguous check in host byte order Christian Göttsche
2024-05-08 17:04 ` Christian Göttsche [this message]
2024-05-15 20:16 ` James Carter
2024-05-24 13:49 ` James Carter
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20240508170422.1396740-2-cgoettsche@seltendoof.de \
--to=cgoettsche@seltendoof.de \
--cc=cgzones@googlemail.com \
--cc=selinux@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).