From ddefe11a409005ee25ee52d954cc2ac674756349 Mon Sep 17 00:00:00 2001 From: Georgia Garcia Date: Tue, 22 Aug 2023 18:12:29 -0300 Subject: [PATCH] parser: add fine grained conditionals to network rule Options available are ip= and port= inside the peer group or outside, representing local addresses and ports: network peer=(ip=127.0.0.1 port=8080), network ip=::1 port=8080 peer=(ip=::2 port=8081), The 'ip' option supports both IPv4 and IPv6. Examples would be ip=192.168.0.4, or ip=::578d The 'port' option accepts a 16-bit unsigned integer. An example would be port=1234 Signed-off-by: Georgia Garcia --- parser/all_rule.cc | 2 +- parser/network.cc | 214 +++++++++++++++++++++++++++++++++++++++---- parser/network.h | 49 +++++++++- parser/parser_yacc.y | 36 +++++--- 4 files changed, 267 insertions(+), 34 deletions(-) diff --git a/parser/all_rule.cc b/parser/all_rule.cc index 89129d92f..34159349e 100644 --- a/parser/all_rule.cc +++ b/parser/all_rule.cc @@ -83,7 +83,7 @@ void all_rule::add_implied_rules(Profile &prof) (void) rule->add_prefix(*prefix); prof.rule_ents.push_back(rule); - rule = new network_rule(0, NULL); + rule = new network_rule(0, (struct cond_entry *)NULL, (struct cond_entry *)NULL); (void) rule->add_prefix(*prefix); prof.rule_ents.push_back(rule); diff --git a/parser/network.cc b/parser/network.cc index f71a0463f..88040c938 100644 --- a/parser/network.cc +++ b/parser/network.cc @@ -20,6 +20,7 @@ #include #include #include +#include #include "lib.h" #include "parser.h" @@ -298,7 +299,59 @@ const struct network_tuple *net_find_mapping(const struct network_tuple *map, return NULL; } -void network_rule::move_conditionals(struct cond_entry *conds) +bool parse_ipv4_address(const char *input, struct ip_address *result) +{ + struct in_addr addr; + if (inet_pton(AF_INET, input, &addr) == 1) { + result->family = AF_INET; + result->address.address_v4 = addr.s_addr; + return true; + } + return false; +} + +bool parse_ipv6_address(const char *input, struct ip_address *result) +{ + struct in6_addr addr; + if (inet_pton(AF_INET6, input, &addr) == 1) { + result->family = AF_INET6; + memcpy(result->address.address_v6, addr.s6_addr, 16); + return true; + } + return false; +} + +bool parse_ip(const char *ip, struct ip_address *result) +{ + return parse_ipv6_address(ip, result) || + parse_ipv4_address(ip, result); +} + +bool parse_port_number(const char *port_entry, uint16_t *port) { + char *eptr; + unsigned long port_tmp = strtoul(port_entry, &eptr, 10); + + if (port_tmp >= 0 && port_entry != eptr && + *eptr == '\0' && port_tmp <= UINT16_MAX) { + *port = port_tmp; + return true; + } + return false; +} + +bool network_rule::parse_port(ip_conds &entry) +{ + entry.is_port = true; + return parse_port_number(entry.sport, &entry.port); +} + +bool network_rule::parse_address(ip_conds &entry) +{ + entry.is_ip = true; + return parse_ip(entry.sip, &entry.ip); +} + +void network_rule::move_conditionals(struct cond_entry *conds, ip_conds &ip_cond) { struct cond_entry *cond_ent; @@ -306,10 +359,18 @@ void network_rule::move_conditionals(struct cond_entry *conds) /* for now disallow keyword 'in' (list) */ if (!cond_ent->eq) yyerror("keyword \"in\" is not allowed in network rules\n"); - - /* no valid conditionals atm */ - yyerror("invalid network rule conditional \"%s\"\n", - cond_ent->name); + if (strcmp(cond_ent->name, "ip") == 0) { + move_conditional_value("network", &ip_cond.sip, cond_ent); + if (!parse_address(ip_cond)) + yyerror("network invalid ip='%s'\n", ip_cond.sip); + } else if (strcmp(cond_ent->name, "port") == 0) { + move_conditional_value("network", &ip_cond.sport, cond_ent); + if (!parse_port(ip_cond)) + yyerror("network invalid port='%s'\n", ip_cond.sport); + } else { + yyerror("invalid network rule conditional \"%s\"\n", + cond_ent->name); + } } } @@ -322,7 +383,8 @@ void network_rule::set_netperm(unsigned int family, unsigned int type) network_perms[family] |= 1 << type; } -network_rule::network_rule(perms_t perms_p, struct cond_entry *conds): +network_rule::network_rule(perms_t perms_p, struct cond_entry *conds, + struct cond_entry *peer_conds): dedup_perms_rule_t(AA_CLASS_NETV8) { size_t family_index; @@ -331,21 +393,25 @@ network_rule::network_rule(perms_t perms_p, struct cond_entry *conds): set_netperm(family_index, 0xFFFFFFFF); } - move_conditionals(conds); + move_conditionals(conds, local); + move_conditionals(peer_conds, peer); free_cond_list(conds); + free_cond_list(peer_conds); if (perms_p) { perms = perms_p; if (perms & ~AA_VALID_NET_PERMS) yyerror("perms contains invalid permissions for network rules\n"); - /* can conds change permission availability? */ + else if ((perms & ~AA_PEER_NET_PERMS) && has_peer_conds()) + yyerror("network 'create', 'shutdown', 'setattr', 'getattr', 'bind', 'listen', 'setopt', and/or 'getopt' accesses cannot be used with peer socket conditionals\n"); } else { perms = AA_VALID_NET_PERMS; } } network_rule::network_rule(perms_t perms_p, const char *family, const char *type, - const char *protocol, struct cond_entry *conds): + const char *protocol, struct cond_entry *conds, + struct cond_entry *peer_conds): dedup_perms_rule_t(AA_CLASS_NETV8) { const struct network_tuple *mapping = NULL; @@ -364,14 +430,17 @@ network_rule::network_rule(perms_t perms_p, const char *family, const char *type if (network_map.empty()) yyerror(_("Invalid network entry.")); - move_conditionals(conds); + move_conditionals(conds, local); + move_conditionals(peer_conds, peer); free_cond_list(conds); + free_cond_list(peer_conds); if (perms_p) { perms = perms_p; if (perms & ~AA_VALID_NET_PERMS) yyerror("perms contains invalid permissions for network rules\n"); - /* can conds change permission availability? */ + else if ((perms & ~AA_PEER_NET_PERMS) && has_peer_conds()) + yyerror("network 'create', 'shutdown', 'setattr', 'getattr', 'bind', 'listen', 'setopt', and/or 'getopt' accesses cannot be used with peer socket conditionals\n"); } else { perms = AA_VALID_NET_PERMS; } @@ -387,7 +456,8 @@ network_rule::network_rule(perms_t perms_p, unsigned int family, unsigned int ty perms = perms_p; if (perms & ~AA_VALID_NET_PERMS) yyerror("perms contains invalid permissions for network rules\n"); - /* can conds change permission availability? */ + else if ((perms & ~AA_PEER_NET_PERMS) && has_peer_conds()) + yyerror("network 'create', 'shutdown', 'setattr', 'getattr', 'bind', 'listen', 'setopt', and/or 'getopt' accesses cannot be used with peer socket conditionals\n"); } else { perms = AA_VALID_NET_PERMS; } @@ -455,6 +525,79 @@ void network_rule::warn_once(const char *name) rule_t::warn_once(name, "network rules not enforced"); } +std::string gen_ip_cond(const struct ip_address ip) +{ + std::ostringstream oss; + int i; + if (ip.family == AF_INET) { + /* add a byte containing the size of the following ip */ + oss << "\\x04"; + + u8 *byte = (u8 *) &ip.address.address_v4; /* in network byte order */ + for (i = 0; i < 4; i++) + oss << "\\x" << std::setfill('0') << std::setw(2) << std::hex << static_cast(byte[i]); + } else { + /* add a byte containing the size of the following ip */ + oss << "\\x10"; + for (i = 0; i < 16; ++i) + oss << "\\x" << std::setfill('0') << std::setw(2) << std::hex << static_cast(ip.address.address_v6[i]); + } + return oss.str(); +} + +std::string gen_port_cond(uint16_t port) +{ + std::ostringstream oss; + if (port > 0) { + oss << "\\x" << std::setfill('0') << std::setw(2) << std::hex << ((port & 0xff00) >> 8); + oss << "\\x" << std::setfill('0') << std::setw(2) << std::hex << (port & 0xff); + } else { + oss << ".."; + } + return oss.str(); +} + +void network_rule::gen_ip_conds(std::ostringstream &oss, ip_conds entry, bool is_peer, bool is_cmd) +{ + /* encode protocol */ + if (!is_cmd) { + if (entry.is_ip) { + oss << "\\x" << std::setfill('0') << std::setw(2) << std::hex << ((entry.ip.family & 0xff00) >> 8); + oss << "\\x" << std::setfill('0') << std::setw(2) << std::hex << (entry.ip.family & 0xff); + } else { + oss << ".."; + } + } + + if (entry.is_port) { + /* encode port type (privileged - 1, remote - 2, unprivileged - 0) */ + if (!is_peer && perms & AA_NET_BIND && entry.port < IPPORT_RESERVED) + oss << "\\x01"; + else if (is_peer) + oss << "\\x02"; + else + oss << "\\x00"; + + oss << gen_port_cond(entry.port); + } else { + /* port type + port number */ + if (!is_cmd) + oss << "."; + oss << ".."; + } + + if (entry.is_ip) { + oss << gen_ip_cond(entry.ip); + } else { + /* encode 0 to indicate there's no ip (ip size) */ + oss << "\\x00"; + } + + oss << "\\-x01"; /* oob separator */ + oss << default_match_pattern; /* label - not used for now */ + oss << "\\x00"; /* null transition */ +} + bool network_rule::gen_net_rule(Profile &prof, u16 family, unsigned int type_mask) { std::ostringstream buffer; std::string buf; @@ -468,13 +611,50 @@ bool network_rule::gen_net_rule(Profile &prof, u16 family, unsigned int type_mas buffer << "\\x" << std::setfill('0') << std::setw(2) << std::hex << ((type_mask & 0xff00) >> 8); buffer << "\\x" << std::setfill('0') << std::setw(2) << std::hex << (type_mask & 0xff); } - buf = buffer.str(); - if (!prof.policy.rules->add_rule(buf.c_str(), rule_mode == RULE_DENY, map_perms(AA_VALID_NET_PERMS), - dedup_perms_rule_t::audit == AUDIT_FORCE ? map_perms(AA_VALID_NET_PERMS) : 0, - parseopts)) - return false; + if (perms & AA_PEER_NET_PERMS) { + gen_ip_conds(buffer, peer, true, false); + buffer << "\\x" << std::setfill('0') << std::setw(2) << std::hex << CMD_ADDR; + + gen_ip_conds(buffer, local, false, true); + + buf = buffer.str(); + if (!prof.policy.rules->add_rule(buf.c_str(), rule_mode == RULE_DENY, map_perms(perms), + dedup_perms_rule_t::audit == AUDIT_FORCE ? map_perms(perms) : 0, + parseopts)) + return false; + } + if ((perms & AA_NET_LISTEN) || (perms & AA_NET_OPT)) { + gen_ip_conds(buffer, local, false, false); + + if (perms & AA_NET_LISTEN) { + std::ostringstream cmd_buffer; + cmd_buffer << buffer.str(); + cmd_buffer << "\\x" << std::setfill('0') << std::setw(2) << std::hex << CMD_LISTEN; + /* length of queue allowed - not used for now */ + cmd_buffer << ".."; + buf = cmd_buffer.str(); + if (!prof.policy.rules->add_rule(buf.c_str(), rule_mode == RULE_DENY, map_perms(perms), + dedup_perms_rule_t::audit == AUDIT_FORCE ? map_perms(perms) : 0, + parseopts)) + return false; + } + if (perms & AA_NET_OPT) { + std::ostringstream cmd_buffer; + cmd_buffer << buffer.str(); + cmd_buffer << "\\x" << std::setfill('0') << std::setw(2) << std::hex << CMD_OPT; + /* level - not used for now */ + cmd_buffer << ".."; + /* socket mapping - not used for now */ + cmd_buffer << ".."; + buf = cmd_buffer.str(); + if (!prof.policy.rules->add_rule(buf.c_str(), rule_mode == RULE_DENY, map_perms(perms), + dedup_perms_rule_t::audit == AUDIT_FORCE ? map_perms(perms) : 0, + parseopts)) + return false; + } + } return true; } diff --git a/parser/network.h b/parser/network.h index 473444400..48f99b12a 100644 --- a/parser/network.h +++ b/parser/network.h @@ -75,6 +75,10 @@ #define AA_PEER_NET_PERMS (AA_VALID_NET_PERMS & (~AA_LOCAL_NET_PERMS | \ AA_NET_ACCEPT)) +#define CMD_ADDR 1 +#define CMD_LISTEN 2 +#define CMD_OPT 4 + struct network_tuple { const char *family_name; unsigned int family; @@ -104,22 +108,58 @@ int net_find_type_val(const char *type); const char *net_find_type_name(int type); const char *net_find_af_name(unsigned int af); +struct ip_address { + union { + uint8_t address_v6[16]; + uint32_t address_v4; + } address; + uint16_t family; +}; + +class ip_conds { +public: + char *sip = NULL; + char *sport = NULL; + + bool is_ip = false; + bool is_port = false; + + uint16_t port; + struct ip_address ip; + + void free_conds() { + if (sip) + free(sip); + if (sport) + free(sport); + } +}; + class network_rule: public dedup_perms_rule_t { - void move_conditionals(struct cond_entry *conds); + void move_conditionals(struct cond_entry *conds, ip_conds &ip_cond); public: std::unordered_map> network_map; std::unordered_map network_perms; + ip_conds peer; + ip_conds local; + + bool has_local_conds(void) { return local.sip || local.sport; } + bool has_peer_conds(void) { return peer.sip || peer.sport; } /* empty constructor used only for the profile to access * static elements to maintain compatibility with * AA_CLASS_NET */ network_rule(): dedup_perms_rule_t(AA_CLASS_NETV8) { } - network_rule(perms_t perms_p, struct cond_entry *conds); + network_rule(perms_t perms_p, struct cond_entry *conds, + struct cond_entry *peer_conds); network_rule(perms_t perms_p, const char *family, const char *type, - const char *protocol, struct cond_entry *conds); + const char *protocol, struct cond_entry *conds, + struct cond_entry *peer_conds); network_rule(perms_t perms_p, unsigned int family, unsigned int type); virtual ~network_rule() { + peer.free_conds(); + local.free_conds(); if (allow) { free(allow); allow = NULL; @@ -138,9 +178,12 @@ public: } }; + void gen_ip_conds(std::ostringstream &oss, ip_conds entry, bool is_peer, bool is_cmd); bool gen_net_rule(Profile &prof, u16 family, unsigned int type_mask); void set_netperm(unsigned int family, unsigned int type); void update_compat_net(void); + bool parse_address(ip_conds &entry); + bool parse_port(ip_conds &entry); virtual bool valid_prefix(const prefixes &p, const char *&error) { if (p.owner) { diff --git a/parser/parser_yacc.y b/parser/parser_yacc.y index 417adee6e..b225fb5d0 100644 --- a/parser/parser_yacc.y +++ b/parser/parser_yacc.y @@ -1083,24 +1083,20 @@ link_rule: TOK_LINK opt_subset_flag id_or_var TOK_ARROW id_or_var TOK_END_OF_RUL $$ = entry; }; -network_rule: TOK_NETWORK opt_net_perm opt_cond_list TOK_END_OF_RULE +network_rule: TOK_NETWORK opt_net_perm opt_conds opt_cond_list TOK_END_OF_RULE { network_rule *entry; - entry = new network_rule($2, $3.list); + if ($4.name) { + if (strcmp($4.name, "peer") != 0) + yyerror(_("network rule: invalid conditional group %s=()"), $4.name); + free($4.name); + } + entry = new network_rule($2, $3, $4.list); $$ = entry; } -network_rule: TOK_NETWORK opt_net_perm TOK_ID opt_cond_list TOK_END_OF_RULE - { - network_rule *entry; - - entry = new network_rule($2, $3, NULL, NULL, $4.list); - free($3); - $$ = entry; - } - -network_rule: TOK_NETWORK opt_net_perm TOK_ID TOK_ID opt_cond_list TOK_END_OF_RULE +network_rule: TOK_NETWORK opt_net_perm TOK_ID opt_conds opt_cond_list TOK_END_OF_RULE { network_rule *entry; @@ -1109,7 +1105,21 @@ network_rule: TOK_NETWORK opt_net_perm TOK_ID TOK_ID opt_cond_list TOK_END_OF_RU yyerror(_("network rule: invalid conditional group %s=()"), $5.name); free($5.name); } - entry = new network_rule($2, $3, $4, NULL, $5.list); + entry = new network_rule($2, $3, NULL, NULL, $4, $5.list); + free($3); + $$ = entry; + } + +network_rule: TOK_NETWORK opt_net_perm TOK_ID TOK_ID opt_conds opt_cond_list TOK_END_OF_RULE + { + network_rule *entry; + + if ($6.name) { + if (strcmp($6.name, "peer") != 0) + yyerror(_("network rule: invalid conditional group %s=()"), $6.name); + free($6.name); + } + entry = new network_rule($2, $3, $4, NULL, $5, $6.list); free($3); free($4); $$ = entry;