mirror of
https://gitlab.com/apparmor/apparmor.git
synced 2025-03-04 16:35:02 +01:00

Currently the apparmor parser warns about rules that are not enforced or downgraded. This is a problem for distros that are not carrying the out of tree kernel patches, as most profile loads result in warnings. Change the behavior to not output a message unless a warn flag is passed. This patch adds 2 different warn flags --warn rule-downgraded # warn if a rule is downgraded --warn rule-not-enforced # warn if a rule is not enforced at all If the warnings are desired by default the flags can be set in the parser.conf file. v2 of patch - update man page - add --warn to usage statement - make --quiet clear warn flags Signed-off-by: John Johansen <john.johansen@canonical.com> Acked-by: Steve Beattie <steve@nxnw.org>
416 lines
11 KiB
C++
416 lines
11 KiB
C++
/*
|
|
* Copyright (c) 2014
|
|
* Canonical, Ltd. (All rights reserved)
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of version 2 of the GNU General Public
|
|
* License published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, contact Novell, Inc. or Canonical
|
|
* Ltd.
|
|
*/
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/apparmor.h>
|
|
|
|
#include <iomanip>
|
|
#include <string>
|
|
#include <iostream>
|
|
#include <sstream>
|
|
|
|
#include "network.h"
|
|
#include "parser.h"
|
|
#include "profile.h"
|
|
#include "af_unix.h"
|
|
|
|
int parse_unix_mode(const char *str_mode, int *mode, int fail)
|
|
{
|
|
return parse_X_mode("unix", AA_VALID_NET_PERMS, str_mode, mode, fail);
|
|
}
|
|
|
|
|
|
static struct supported_cond supported_conds[] = {
|
|
{ "addr", true, false, false, either_cond },
|
|
{ NULL, false, false, false, local_cond }, /* sentinal */
|
|
};
|
|
|
|
void unix_rule::move_conditionals(struct cond_entry *conds)
|
|
{
|
|
struct cond_entry *ent;
|
|
|
|
list_for_each(conds, ent) {
|
|
|
|
if (!cond_check(supported_conds, ent, false, "unix") &&
|
|
!move_base_cond(ent, false)) {
|
|
yyerror("unix rule: invalid conditional '%s'\n",
|
|
ent->name);
|
|
continue;
|
|
}
|
|
if (strcmp(ent->name, "addr") == 0) {
|
|
move_conditional_value("unix socket", &addr, ent);
|
|
if (addr[0] != '@' && strcmp(addr, "none") != 0)
|
|
yyerror("unix rule: invalid value for addr='%s'\n", addr);
|
|
}
|
|
|
|
/* TODO: add conditionals for
|
|
* listen queue length
|
|
* attrs that can be read/set
|
|
* ops that can be read/set
|
|
* allow in on
|
|
* type, protocol
|
|
* local label match, and set
|
|
*/
|
|
}
|
|
}
|
|
|
|
void unix_rule::move_peer_conditionals(struct cond_entry *conds)
|
|
{
|
|
struct cond_entry *ent;
|
|
|
|
list_for_each(conds, ent) {
|
|
if (!cond_check(supported_conds, ent, true, "unix") &&
|
|
!move_base_cond(ent, true)) {
|
|
yyerror("unix rule: invalid peer conditional '%s'\n",
|
|
ent->name);
|
|
continue;
|
|
}
|
|
if (strcmp(ent->name, "addr") == 0) {
|
|
move_conditional_value("unix", &peer_addr, ent);
|
|
if (peer_addr[0] != '@' && strcmp(peer_addr, "none") != 0)
|
|
yyerror("unix rule: invalid value for addr='%s'\n", peer_addr);
|
|
}
|
|
}
|
|
}
|
|
|
|
unix_rule::unix_rule(unsigned int type_p, bool audit_p, bool denied):
|
|
af_rule("unix"), addr(NULL), peer_addr(NULL)
|
|
{
|
|
if (type_p != 0xffffffff) {
|
|
sock_type_n = type_p;
|
|
sock_type = strdup(net_find_type_name(type_p));
|
|
if (!sock_type)
|
|
yyerror("socket rule: invalid socket type '%d'", type_p);
|
|
}
|
|
mode = AA_VALID_NET_PERMS;
|
|
audit = audit_p ? AA_VALID_NET_PERMS : 0;
|
|
deny = denied;
|
|
}
|
|
|
|
unix_rule::unix_rule(int mode_p, struct cond_entry *conds,
|
|
struct cond_entry *peer_conds):
|
|
af_rule("unix"), addr(NULL), peer_addr(NULL),
|
|
audit(0), deny(0)
|
|
{
|
|
move_conditionals(conds);
|
|
move_peer_conditionals(peer_conds);
|
|
|
|
if (mode_p) {
|
|
mode = mode_p;
|
|
if (mode & ~AA_VALID_NET_PERMS)
|
|
yyerror("mode contains invalid permissions for unix socket rules\n");
|
|
else if ((mode & ~AA_PEER_NET_PERMS) && has_peer_conds())
|
|
yyerror("unix socket 'create', 'shutdown', 'setattr', 'getattr', 'bind', 'listen', 'setopt', and/or 'getopt' accesses cannot be used with peer socket conditionals\n");
|
|
} else {
|
|
mode = AA_VALID_NET_PERMS;
|
|
}
|
|
|
|
free_cond_list(conds);
|
|
free_cond_list(peer_conds);
|
|
|
|
}
|
|
|
|
ostream &unix_rule::dump_local(ostream &os)
|
|
{
|
|
af_rule::dump_local(os);
|
|
if (addr)
|
|
os << "addr='" << addr << "'";
|
|
return os;
|
|
}
|
|
|
|
ostream &unix_rule::dump_peer(ostream &os)
|
|
{
|
|
af_rule::dump_peer(os);
|
|
if (peer_addr)
|
|
os << "addr='" << peer_addr << "'";
|
|
return os;
|
|
}
|
|
|
|
|
|
int unix_rule::expand_variables(void)
|
|
{
|
|
int error = af_rule::expand_variables();
|
|
if (error)
|
|
return error;
|
|
error = expand_entry_variables(&addr);
|
|
if (error)
|
|
return error;
|
|
error = expand_entry_variables(&peer_addr);
|
|
if (error)
|
|
return error;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* do we want to warn once/profile or just once per compile?? */
|
|
static void warn_once(const char *name, const char *msg)
|
|
{
|
|
static const char *warned_name = NULL;
|
|
|
|
if (warned_name != name) {
|
|
cerr << "Warning from profile " << name << " (";
|
|
if (current_filename)
|
|
cerr << current_filename;
|
|
else
|
|
cerr << "stdin";
|
|
cerr << "): " << msg << "\n";
|
|
warned_name = name;
|
|
}
|
|
}
|
|
|
|
static void warn_once(const char *name)
|
|
{
|
|
if (warnflags & WARN_RULE_NOT_ENFORCED)
|
|
warn_once(name, "extended network unix socket rules not enforced");
|
|
}
|
|
|
|
static void writeu16(std::ostringstream &o, int v)
|
|
{
|
|
u16 tmp = htobe16((u16) v);
|
|
u8 *byte1 = (u8 *)&tmp;
|
|
u8 *byte2 = byte1 + 1;
|
|
|
|
o << "\\x" << std::setfill('0') << std::setw(2) << std::hex << static_cast<unsigned int>(*byte1);
|
|
o << "\\x" << std::setfill('0') << std::setw(2) << std::hex << static_cast<unsigned int>(*byte2);
|
|
}
|
|
|
|
#define CMD_ADDR 1
|
|
#define CMD_LISTEN 2
|
|
#define CMD_ACCEPT 3
|
|
#define CMD_OPT 4
|
|
|
|
void unix_rule::downgrade_rule(Profile &prof) {
|
|
if (!prof.net.allow && !prof.alloc_net_table())
|
|
yyerror(_("Memory allocation error."));
|
|
if (deny) {
|
|
prof.net.deny[AF_UNIX] |= 1 << sock_type_n;
|
|
if (!audit)
|
|
prof.net.quiet[AF_UNIX] |= 1 << sock_type_n;
|
|
} else {
|
|
prof.net.allow[AF_UNIX] |= 1 << sock_type_n;
|
|
if (audit)
|
|
prof.net.audit[AF_UNIX] |= 1 << sock_type_n;
|
|
}
|
|
}
|
|
|
|
static uint32_t map_perms(uint32_t mask)
|
|
{
|
|
return (mask & 0x7f) |
|
|
((mask & (AA_NET_GETATTR | AA_NET_SETATTR)) << (AA_OTHER_SHIFT - 8)) |
|
|
((mask & (AA_NET_ACCEPT | AA_NET_BIND | AA_NET_LISTEN)) >> 4) | /* 2 + (AA_OTHER_SHIFT - 20) */
|
|
((mask & (AA_NET_SETOPT | AA_NET_GETOPT)) >> 5); /* 5 + (AA_OTHER_SHIFT - 24) */
|
|
}
|
|
|
|
void unix_rule::write_to_prot(std::ostringstream &buffer)
|
|
{
|
|
buffer << "\\x" << std::setfill('0') << std::setw(2) << std::hex << AA_CLASS_NET;
|
|
writeu16(buffer, AF_UNIX);
|
|
if (sock_type)
|
|
writeu16(buffer, sock_type_n);
|
|
else
|
|
buffer << "..";
|
|
if (proto)
|
|
writeu16(buffer, proto_n);
|
|
else
|
|
buffer << "..";
|
|
}
|
|
|
|
bool unix_rule::write_addr(std::ostringstream &buffer, const char *addr)
|
|
{
|
|
std::string buf;
|
|
pattern_t ptype;
|
|
|
|
if (addr) {
|
|
int pos;
|
|
if (strcmp(addr, "none") == 0) {
|
|
/* anonymous */
|
|
buffer << "\\x01";
|
|
} else {
|
|
/* skip leading @ */
|
|
ptype = convert_aaregex_to_pcre(addr + 1, 0, buf, &pos);
|
|
if (ptype == ePatternInvalid)
|
|
return false;
|
|
/* kernel starts abstract with \0 */
|
|
buffer << "\\x00";
|
|
buffer << buf;
|
|
}
|
|
} else
|
|
/* match any addr or anonymous */
|
|
buffer << ".*";
|
|
|
|
/* todo: change to out of band separator */
|
|
buffer << "\\x00";
|
|
|
|
return true;
|
|
}
|
|
|
|
bool unix_rule::write_label(std::ostringstream &buffer, const char *label)
|
|
{
|
|
std::string buf;
|
|
pattern_t ptype;
|
|
|
|
if (label) {
|
|
int pos;
|
|
ptype = convert_aaregex_to_pcre(label, 0, buf, &pos);
|
|
if (ptype == ePatternInvalid)
|
|
return false;
|
|
/* kernel starts abstract with \0 */
|
|
buffer << buf;
|
|
} else
|
|
buffer << default_match_pattern;
|
|
|
|
return true;
|
|
}
|
|
|
|
/* General Layout
|
|
*
|
|
* Local socket end point perms
|
|
* CLASS_NET AF TYPE PROTO local (addr\0label) \0 cmd cmd_option
|
|
* ^ ^ ^ ^ ^
|
|
* | | | | |
|
|
* stub perm | | | |
|
|
* | | | |
|
|
* sub stub perm | | |
|
|
* | | |
|
|
* create perm | |
|
|
* | |
|
|
* | |
|
|
* bind, accept, get/set attr |
|
|
* |
|
|
* listen, set/get opt perm
|
|
*
|
|
*
|
|
* peer socket end point perms
|
|
* CLASS_NET AF TYPE PROTO local(addr\0label\0) cmd_addr peer(addr\0label )
|
|
* ^
|
|
* |
|
|
* send/receive connect/accept perm
|
|
*
|
|
* NOTE: accept is encoded twice, locally to check if a socket is allowed
|
|
* to accept, and then as a pair to test that it can accept the pair.
|
|
*/
|
|
int unix_rule::gen_policy_re(Profile &prof)
|
|
{
|
|
std::ostringstream buffer;
|
|
std::string buf;
|
|
|
|
int mask = mode;
|
|
|
|
/* always generate a downgraded rule. This doesn't change generated
|
|
* policy size and allows the binary policy to be loaded against
|
|
* older kernels and be enforced to the best of the old network
|
|
* rules ability
|
|
*/
|
|
downgrade_rule(prof);
|
|
if (!kernel_supports_unix) {
|
|
if (kernel_supports_network) {
|
|
/* only warn if we are building against a kernel
|
|
* that requires downgrading */
|
|
if (warnflags & WARN_RULE_DOWNGRADED)
|
|
warn_once(prof.name, "downgrading extended network unix socket rule to generic network rule\n");
|
|
/* TODO: add ability to abort instead of downgrade */
|
|
return RULE_OK;
|
|
}
|
|
warn_once(prof.name);
|
|
return RULE_NOT_SUPPORTED;
|
|
}
|
|
|
|
write_to_prot(buffer);
|
|
if ((mask & AA_NET_CREATE) && !has_peer_conds()) {
|
|
buf = buffer.str();
|
|
if (!prof.policy.rules->add_rule(buf.c_str(), deny,
|
|
map_perms(AA_NET_CREATE),
|
|
map_perms(audit & AA_NET_CREATE),
|
|
dfaflags))
|
|
goto fail;
|
|
mask &= ~AA_NET_CREATE;
|
|
}
|
|
|
|
if (mask) {
|
|
/* local addr */
|
|
if (!write_addr(buffer, addr))
|
|
goto fail;
|
|
/* local label option */
|
|
if (!write_label(buffer, label))
|
|
goto fail;
|
|
/* seperator */
|
|
buffer << "\\x00";
|
|
|
|
/* create already masked off */
|
|
int local_mask = has_peer_conds() ? AA_NET_ACCEPT :
|
|
AA_LOCAL_NET_PERMS & ~AA_LOCAL_NET_CMD;
|
|
if (mask & local_mask) {
|
|
buf = buffer.str();
|
|
if (!prof.policy.rules->add_rule(buf.c_str(), deny,
|
|
map_perms(mask & local_mask),
|
|
map_perms(audit & local_mask),
|
|
dfaflags))
|
|
goto fail;
|
|
}
|
|
|
|
if ((mask & AA_NET_LISTEN) && !has_peer_conds()) {
|
|
std::ostringstream tmp(buffer.str());
|
|
tmp.seekp(0, ios_base::end);
|
|
tmp << "\\x" << std::setfill('0') << std::setw(2) << std::hex << CMD_LISTEN;
|
|
/* TODO: backlog conditional: for now match anything*/
|
|
tmp << "..";
|
|
buf = tmp.str();
|
|
if (!prof.policy.rules->add_rule(buf.c_str(), deny,
|
|
map_perms(AA_NET_LISTEN),
|
|
map_perms(audit & AA_NET_LISTEN),
|
|
dfaflags))
|
|
goto fail;
|
|
}
|
|
if ((mask & AA_NET_OPT) && !has_peer_conds()) {
|
|
std::ostringstream tmp(buffer.str());
|
|
tmp.seekp(0, ios_base::end);
|
|
tmp << "\\x" << std::setfill('0') << std::setw(2) << std::hex << CMD_OPT;
|
|
/* TODO: sockopt conditional: for now match anything */
|
|
tmp << "..";
|
|
buf = tmp.str();
|
|
if (!prof.policy.rules->add_rule(buf.c_str(), deny,
|
|
map_perms(mask & AA_NET_OPT),
|
|
map_perms(audit & AA_NET_OPT),
|
|
dfaflags))
|
|
goto fail;
|
|
}
|
|
mask &= ~AA_LOCAL_NET_PERMS | AA_NET_ACCEPT;
|
|
} /* if (mask) */
|
|
|
|
if (mask & AA_PEER_NET_PERMS) {
|
|
/* cmd selector */
|
|
buffer << "\\x" << std::setfill('0') << std::setw(2) << std::hex << CMD_ADDR;
|
|
|
|
/* local addr */
|
|
if (!write_addr(buffer, peer_addr))
|
|
goto fail;
|
|
/* local label option */
|
|
if (!write_label(buffer, peer_label))
|
|
goto fail;
|
|
|
|
buf = buffer.str();
|
|
if (!prof.policy.rules->add_rule(buf.c_str(), deny, map_perms(mode & AA_PEER_NET_PERMS), map_perms(audit), dfaflags))
|
|
goto fail;
|
|
}
|
|
|
|
return RULE_OK;
|
|
|
|
fail:
|
|
return RULE_ERROR;
|
|
}
|