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 <georgia.garcia@canonical.com>
This commit is contained in:
Georgia Garcia 2023-08-22 18:12:29 -03:00
parent 746f76d3e1
commit ddefe11a40
4 changed files with 267 additions and 34 deletions

View file

@ -83,7 +83,7 @@ void all_rule::add_implied_rules(Profile &prof)
(void) rule->add_prefix(*prefix); (void) rule->add_prefix(*prefix);
prof.rule_ents.push_back(rule); 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); (void) rule->add_prefix(*prefix);
prof.rule_ents.push_back(rule); prof.rule_ents.push_back(rule);

View file

@ -20,6 +20,7 @@
#include <string> #include <string>
#include <sstream> #include <sstream>
#include <map> #include <map>
#include <arpa/inet.h>
#include "lib.h" #include "lib.h"
#include "parser.h" #include "parser.h"
@ -298,7 +299,59 @@ const struct network_tuple *net_find_mapping(const struct network_tuple *map,
return NULL; 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; struct cond_entry *cond_ent;
@ -306,10 +359,18 @@ void network_rule::move_conditionals(struct cond_entry *conds)
/* for now disallow keyword 'in' (list) */ /* for now disallow keyword 'in' (list) */
if (!cond_ent->eq) if (!cond_ent->eq)
yyerror("keyword \"in\" is not allowed in network rules\n"); yyerror("keyword \"in\" is not allowed in network rules\n");
if (strcmp(cond_ent->name, "ip") == 0) {
/* no valid conditionals atm */ move_conditional_value("network", &ip_cond.sip, cond_ent);
yyerror("invalid network rule conditional \"%s\"\n", if (!parse_address(ip_cond))
cond_ent->name); 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_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) dedup_perms_rule_t(AA_CLASS_NETV8)
{ {
size_t family_index; 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); set_netperm(family_index, 0xFFFFFFFF);
} }
move_conditionals(conds); move_conditionals(conds, local);
move_conditionals(peer_conds, peer);
free_cond_list(conds); free_cond_list(conds);
free_cond_list(peer_conds);
if (perms_p) { if (perms_p) {
perms = perms_p; perms = perms_p;
if (perms & ~AA_VALID_NET_PERMS) if (perms & ~AA_VALID_NET_PERMS)
yyerror("perms contains invalid permissions for network rules\n"); 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 { } else {
perms = AA_VALID_NET_PERMS; perms = AA_VALID_NET_PERMS;
} }
} }
network_rule::network_rule(perms_t perms_p, const char *family, const char *type, 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) dedup_perms_rule_t(AA_CLASS_NETV8)
{ {
const struct network_tuple *mapping = NULL; 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()) if (network_map.empty())
yyerror(_("Invalid network entry.")); yyerror(_("Invalid network entry."));
move_conditionals(conds); move_conditionals(conds, local);
move_conditionals(peer_conds, peer);
free_cond_list(conds); free_cond_list(conds);
free_cond_list(peer_conds);
if (perms_p) { if (perms_p) {
perms = perms_p; perms = perms_p;
if (perms & ~AA_VALID_NET_PERMS) if (perms & ~AA_VALID_NET_PERMS)
yyerror("perms contains invalid permissions for network rules\n"); 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 { } else {
perms = AA_VALID_NET_PERMS; 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; perms = perms_p;
if (perms & ~AA_VALID_NET_PERMS) if (perms & ~AA_VALID_NET_PERMS)
yyerror("perms contains invalid permissions for network rules\n"); 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 { } else {
perms = AA_VALID_NET_PERMS; 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"); 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<unsigned int>(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<unsigned int>(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) { bool network_rule::gen_net_rule(Profile &prof, u16 family, unsigned int type_mask) {
std::ostringstream buffer; std::ostringstream buffer;
std::string buf; 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 & 0xff00) >> 8);
buffer << "\\x" << std::setfill('0') << std::setw(2) << std::hex << (type_mask & 0xff); 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), if (perms & AA_PEER_NET_PERMS) {
dedup_perms_rule_t::audit == AUDIT_FORCE ? map_perms(AA_VALID_NET_PERMS) : 0, gen_ip_conds(buffer, peer, true, false);
parseopts))
return 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; return true;
} }

View file

@ -75,6 +75,10 @@
#define AA_PEER_NET_PERMS (AA_VALID_NET_PERMS & (~AA_LOCAL_NET_PERMS | \ #define AA_PEER_NET_PERMS (AA_VALID_NET_PERMS & (~AA_LOCAL_NET_PERMS | \
AA_NET_ACCEPT)) AA_NET_ACCEPT))
#define CMD_ADDR 1
#define CMD_LISTEN 2
#define CMD_OPT 4
struct network_tuple { struct network_tuple {
const char *family_name; const char *family_name;
unsigned int family; 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_type_name(int type);
const char *net_find_af_name(unsigned int af); 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 { 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: public:
std::unordered_map<unsigned int, std::vector<struct aa_network_entry>> network_map; std::unordered_map<unsigned int, std::vector<struct aa_network_entry>> network_map;
std::unordered_map<unsigned int, perms_t> network_perms; std::unordered_map<unsigned int, perms_t> 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 /* empty constructor used only for the profile to access
* static elements to maintain compatibility with * static elements to maintain compatibility with
* AA_CLASS_NET */ * AA_CLASS_NET */
network_rule(): dedup_perms_rule_t(AA_CLASS_NETV8) { } 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, 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); network_rule(perms_t perms_p, unsigned int family, unsigned int type);
virtual ~network_rule() virtual ~network_rule()
{ {
peer.free_conds();
local.free_conds();
if (allow) { if (allow) {
free(allow); free(allow);
allow = NULL; 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); bool gen_net_rule(Profile &prof, u16 family, unsigned int type_mask);
void set_netperm(unsigned int family, unsigned int type); void set_netperm(unsigned int family, unsigned int type);
void update_compat_net(void); 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) { virtual bool valid_prefix(const prefixes &p, const char *&error) {
if (p.owner) { if (p.owner) {

View file

@ -1083,24 +1083,20 @@ link_rule: TOK_LINK opt_subset_flag id_or_var TOK_ARROW id_or_var TOK_END_OF_RUL
$$ = entry; $$ = 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; 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; $$ = entry;
} }
network_rule: TOK_NETWORK opt_net_perm 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;
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 *entry; 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); yyerror(_("network rule: invalid conditional group %s=()"), $5.name);
free($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($3);
free($4); free($4);
$$ = entry; $$ = entry;