From 804e52b7f8a3c8649615211a961ef8189fe73f39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20G=C3=B6ttsche?= Date: Wed, 8 May 2024 19:04:22 +0200 Subject: [PATCH] checkpolicy: support CIDR notation for nodecon statements MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 Acked-by: James Carter --- 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