mirror of
https://gitlab.com/apparmor/apparmor.git
synced 2025-03-04 08:24:42 +01:00

This patch implements parsing of fine grained mediation for unix domain sockets, that have abstract and anonymous paths. Sockets with file system paths are handled by regular file access rules. The unix network rules follow the general fine grained network rule pattern of [<qualifiers>] af_name [<access expr>] [<rule conds>] [<local expr>] [<peer expr>] specifically for af_unix this is [<qualifiers>] 'unix' [<access expr>] [<rule conds>] [<local expr>] [<peer expr>] <qualifiers> = [ 'audit' ] [ 'allow' | 'deny' ] <access expr> = ( <access> | <access list> ) <access> = ( 'server' | 'create' | 'bind' | 'listen' | 'accept' | 'connect' | 'shutdown' | 'getattr' | 'setattr' | 'getopt' | 'setopt' | 'send' | 'receive' | 'r' | 'w' | 'rw' ) (some access modes are incompatible with some rules or require additional parameters) <access list> = '(' <access> ( [','] <WS> <access> )* ')' <WS> = white space <rule conds> = ( <type cond> | <protocol cond> )* each cond can appear at most once <type cond> = 'type' '=' ( <AARE> | '(' ( '"' <AARE> '"' | <AARE> )+ ')' ) <protocol cond> = 'protocol' '=' ( <AARE> | '(' ( '"' <AARE> '"' | <AARE> )+ ')' ) <local expr> = ( <path cond> | <attr cond> | <opt cond> )* each cond can appear at most once <peer expr> = 'peer' '=' ( <path cond> | <label cond> )+ each cond can appear at most once <path cond> = 'path' '=' ( <AARE> | '(' '"' <AARE> '"' | <AARE> ')' ) <label cond> = 'label' '=' ( <AARE> | '(' '"' <AARE> '"' | <AARE> ')') <attr cond> = 'attr' '=' ( <AARE> | '(' '"' <AARE> '"' | <AARE> ')' ) <opt cond> = 'opt' '=' ( <AARE> | '(' '"' <AARE> '"' | <AARE> ')' ) <AARE> = ?*[]{}^ ( see man page ) unix domain socket rules are accumulated so that the granted unix socket permissions are the union of all the listed unix rule permissions. unix domain socket rules are broad and general and become more restrictive as further information is specified. Policy may be specified down to the path and label level. The content of the communication is not examined. Some permissions are not compatible with all unix rules. unix socket rule permissions are implied when a rule does not explicitly state an access list. By default if a rule does not have an access list all permissions that are compatible with the specified set of local and peer conditionals are implied. The 'server', 'r', 'w' and 'rw' permissions are aliases for other permissions. server = (create, bind, listen, accept) r = (receive, getattr, getopt) w = (create, connect, send, setattr, setopt) In addition it supports the v7 kernel abi semantics around generic network rules. The v7 abi removes the masking unix and netlink address families from the generic masking and uses fine grained mediation for an address type if supplied. This means that the rules network unix, network netlink, are now enforced instead of ignored. The parser previously could accept these but the kernel would ignore anything written to them. If a network rule is supplied it takes precedence over the finer grained mediation rule. If permission is not granted via a broad network access rule fine grained mediation is applied. Signed-off-by: John Johansen <john.johansen@canonical.com> Acked-by: Seth Arnold <seth.arnold@canonical.com>
386 lines
9.8 KiB
C++
386 lines
9.8 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[] = {
|
|
{ "path", 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, "path") == 0) {
|
|
move_conditional_value("unix socket", &path, ent);
|
|
if (path[0] != '@' && strcmp(path, "none") != 0)
|
|
yyerror("unix rule: invalid value for path='%s'\n", path);
|
|
}
|
|
|
|
/* 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, "path") == 0) {
|
|
move_conditional_value("unix", &peer_path, ent);
|
|
if (peer_path[0] != '@' && strcmp(path, "none") != 0)
|
|
yyerror("unix rule: invalid value for path='%s'\n", peer_path);
|
|
}
|
|
}
|
|
}
|
|
|
|
unix_rule::unix_rule(unsigned int type_p, bool audit_p, bool denied):
|
|
af_rule("unix"), path(NULL), peer_path(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"), path(NULL), peer_path(NULL)
|
|
{
|
|
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_NET_BIND) &&
|
|
((mode & AA_PEER_NET_PERMS) || has_peer_conds()))
|
|
/* Do we want to loosen this? */
|
|
yyerror("unix socket 'bind' access cannot be used with message rule conditionals\n");
|
|
else if ((mode & AA_NET_LISTEN) &&
|
|
((mode & AA_PEER_NET_PERMS) || has_peer_conds()))
|
|
/* Do we want to loosen this? */
|
|
yyerror("unix socket 'listen' access cannot be used with message rule conditionals\n");
|
|
else if ((mode & AA_NET_ACCEPT) &&
|
|
((mode & AA_PEER_NET_PERMS) || has_peer_conds()))
|
|
/* Do we want to loosen this? */
|
|
yyerror("unix socket 'accept' access cannot be used with message rule 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 (path)
|
|
os << "path='" << path << "'";
|
|
return os;
|
|
}
|
|
|
|
ostream &unix_rule::dump_peer(ostream &os)
|
|
{
|
|
af_rule::dump_peer(os);
|
|
if (peer_path)
|
|
os << "path='" << peer_path << "'";
|
|
return os;
|
|
}
|
|
|
|
|
|
int unix_rule::expand_variables(void)
|
|
{
|
|
int error = af_rule::expand_variables();
|
|
if (error)
|
|
return error;
|
|
error = expand_entry_variables(&path);
|
|
if (error)
|
|
return error;
|
|
error = expand_entry_variables(&peer_path);
|
|
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)
|
|
{
|
|
warn_once(name, "extended network unix socket rules not enforced");
|
|
}
|
|
|
|
std::ostringstream &writeu16(std::ostringstream &o, int v)
|
|
{
|
|
u16 tmp = htobe16((u16) v);
|
|
char *c = (char *) &tmp;
|
|
o << "\\x" << std::setfill('0') << std::setw(2) << std::hex << *c++;
|
|
o << "\\x" << std::setfill('0') << std::setw(2) << std::hex << *c;
|
|
return o;
|
|
}
|
|
|
|
#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;
|
|
}
|
|
}
|
|
|
|
int unix_rule::gen_policy_re(Profile &prof)
|
|
{
|
|
std::ostringstream buffer, tmp;
|
|
std::string buf;
|
|
|
|
pattern_t ptype;
|
|
int pos;
|
|
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 */
|
|
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;
|
|
}
|
|
|
|
|
|
buffer << "\\x" << std::setfill('0') << std::setw(2) << std::hex << AA_CLASS_NET;
|
|
buffer << writeu16(buffer, AF_UNIX);
|
|
if (sock_type)
|
|
buffer << writeu16(buffer, sock_type_n);
|
|
else
|
|
buffer << "..";
|
|
if (proto)
|
|
buffer << writeu16(buffer, proto_n);
|
|
else
|
|
buffer << "..";
|
|
|
|
if (mask & AA_NET_CREATE) {
|
|
buf = buffer.str();
|
|
if (!prof.policy.rules->add_rule(buf.c_str(), deny,
|
|
AA_NET_CREATE,
|
|
audit & AA_NET_CREATE,
|
|
dfaflags))
|
|
goto fail;
|
|
mask &= ~AA_NET_CREATE;
|
|
}
|
|
|
|
/* local addr */
|
|
if (path) {
|
|
if (strcmp(path, "none") == 0) {
|
|
buffer << "\\x01";
|
|
} else {
|
|
/* skip leading @ */
|
|
ptype = convert_aaregex_to_pcre(path + 1, 0, buf, &pos);
|
|
if (ptype == ePatternInvalid)
|
|
goto fail;
|
|
/* kernel starts abstract with \0 */
|
|
buffer << "\\x00";
|
|
buffer << buf;
|
|
}
|
|
} else
|
|
buffer << ".*";
|
|
|
|
/* change to out of band separator */
|
|
buffer << "\\x00";
|
|
|
|
if (mask & AA_LOCAL_NET_PERMS) {
|
|
/* local label option */
|
|
if (label) {
|
|
ptype = convert_aaregex_to_pcre(label, 0, buf, &pos);
|
|
if (ptype == ePatternInvalid)
|
|
goto fail;
|
|
/* kernel starts abstract with \0 */
|
|
buffer << buf;
|
|
} else
|
|
tmp << anyone_match_pattern;
|
|
buffer << "\\x00";
|
|
|
|
/* create already masked off */
|
|
if (mask & AA_LOCAL_NET_PERMS & ~AA_LOCAL_NET_CMD) {
|
|
buf = buffer.str();
|
|
if (!prof.policy.rules->add_rule(buf.c_str(), deny,
|
|
mask & AA_LOCAL_NET_PERMS & ~AA_LOCAL_NET_CMD,
|
|
audit & AA_LOCAL_NET_PERMS & ~AA_LOCAL_NET_CMD,
|
|
dfaflags))
|
|
goto fail;
|
|
}
|
|
|
|
/* cmd selector - drop accept??? */
|
|
if (mask & AA_NET_ACCEPT) {
|
|
tmp.str(buffer.str());
|
|
tmp << "\\x" << std::setfill('0') << std::setw(2) << std::hex << CMD_ACCEPT;
|
|
buf = tmp.str();
|
|
if (!prof.policy.rules->add_rule(buf.c_str(), deny,
|
|
AA_NET_ACCEPT,
|
|
audit & AA_NET_ACCEPT,
|
|
dfaflags))
|
|
goto fail;
|
|
}
|
|
if (mask & AA_NET_LISTEN) {
|
|
tmp.str(buffer.str());
|
|
tmp << "\\x" << std::setfill('0') << std::setw(2) << std::hex << CMD_LISTEN;
|
|
/* TODO: backlog conditional */
|
|
tmp << "..";
|
|
buf = tmp.str();
|
|
if (!prof.policy.rules->add_rule(buf.c_str(), deny,
|
|
AA_NET_LISTEN,
|
|
audit & AA_NET_LISTEN,
|
|
dfaflags))
|
|
goto fail;
|
|
}
|
|
if (mask & AA_NET_OPT) {
|
|
tmp.str(buffer.str());
|
|
tmp << "\\x" << std::setfill('0') << std::setw(2) << std::hex << CMD_OPT;
|
|
/* TODO: sockopt conditional */
|
|
tmp << "..";
|
|
buf = tmp.str();
|
|
if (!prof.policy.rules->add_rule(buf.c_str(), deny,
|
|
AA_NET_OPT,
|
|
audit & AA_NET_OPT,
|
|
dfaflags))
|
|
goto fail;
|
|
}
|
|
mask &= ~AA_LOCAL_NET_PERMS;
|
|
}
|
|
|
|
if (mask & AA_PEER_NET_PERMS) {
|
|
/* cmd selector */
|
|
buffer << "\\x" << std::setfill('0') << std::setw(2) << std::hex << CMD_ADDR;
|
|
|
|
/* peer addr */
|
|
if (peer_path) {
|
|
if (strcmp(path, "none") == 0) {
|
|
buffer << "\\x01";
|
|
} else {
|
|
/* skip leading @ */
|
|
ptype = convert_aaregex_to_pcre(peer_path + 1, 0, buf, &pos);
|
|
if (ptype == ePatternInvalid)
|
|
goto fail;
|
|
/* kernel starts abstract with \0 */
|
|
buffer << "\\x00";
|
|
buffer << buf;
|
|
}
|
|
}
|
|
/* change to out of band separator */
|
|
buffer << "\\x00";
|
|
|
|
if (peer_label) {
|
|
ptype = convert_aaregex_to_pcre(peer_label, 0, buf, &pos);
|
|
if (ptype == ePatternInvalid)
|
|
goto fail;
|
|
buffer << buf;
|
|
} else {
|
|
buffer << anyone_match_pattern;
|
|
}
|
|
|
|
buf = buffer.str();
|
|
if (!prof.policy.rules->add_rule(buf.c_str(), deny, mode & AA_PEER_NET_PERMS, audit, dfaflags))
|
|
goto fail;
|
|
}
|
|
|
|
return RULE_OK;
|
|
|
|
fail:
|
|
return RULE_ERROR;
|
|
}
|